Springmvc集成Shiro实现权限管理
源代码下载:http://download.csdn.net/detail/u013147600/9062175
Shiro是一个安全框架,他可以集成其他开发开发框架 如:Springmvc,实现用户身份认证、权限管理等等功能,
shiro详细的介绍也就不讲了,这里
给出一些关键的知识点吧:
知识点:
shiro中默认的过滤器
过滤器名称 过滤器类 描述 anon org.apache.shiro.web.filter.authc.AnonymousFilter 匿名过滤器 authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter 如果继续操作,需要做对应的表单验证否则不能通过 authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter 基本http验证过滤,如果不通过,跳转屋登录页面 logout org.apache.shiro.web.filter.authc.LogoutFilter 登录退出过滤器 noSessionCreation org.apache.shiro.web.filter.session.NoSessionCreationFilter 没有session创建过滤器 perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter 权限过滤器 port org.apache.shiro.web.filter.authz.PortFilter 端口过滤器,可以设置是否是指定端口如果不是跳转到登录页面 rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter http方法过滤器,可以指定如post不能进行访问等 roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter 角色过滤器,判断当前用户是否指定角色 ssl org.apache.shiro.web.filter.authz.SslFilter 请求需要通过ssl,如果不是跳转回登录页 user org.apache.shiro.web.filter.authc.UserFilter 如果访问一个已知用户,比如记住我功能,走这个过滤器 <!-- 访问 /member/queryMyUserInfo路径需要进行身份认证,并且只有当用户的角色为"member"且权限为"member:delete"时才可以访问该路径-->
/member/queryMyUserInfo=authc,roles[member],perms[member:delete]
Shiro相应的jsp标签:
jsp页面中导入:<%@ taglib prefix= "shiro" uri= "http://shiro.apache.org/tags" %>
- guest标签 验证当前用户是否为“访客”,即未认证(包含未记住)的用户
- user标签 认证通过或已记住的用户
- authenticated标签 已认证通过的用户。不包含已记住的用户,这是与user标签的区别所在。
- notAuthenticated标签 未认证通过用户,与authenticated标签相对应。与guest标签的区别是,该标签包含已记住用户。
- principal 标签 输出当前用户信息,通常为登录帐号信息
- hasRole标签 验证当前用户是否属于该角色
- lacksRole标签 与hasRole标签逻辑相反,当用户不属于该角色时验证通过
- hasAnyRole标签 验证当前用户是否属于以下任意一个角色。
- hasPermission标签 验证当前用户是否拥有制定权限
- lacksPermission标签 与hasPermission标签逻辑相反,当前用户没有制定权限时,验证通过
Ehcache缓存:
<!--
- name:Cache的唯一标识
- maxElementsInMemory:内存中最大缓存对象数
- maxElementsOnDisk:磁盘中最大缓存对象数,若是0表示无穷大
- eternal:Element是否永久有效,一但设置了,timeout将不起作用
- overflowToDisk:配置此属性,当内存中Element数量达到maxElementsInMemory时,Ehcache将会Element写到磁盘中
- timeToIdleSeconds:设置Element在失效前的允许闲置时间。仅当element不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大
- timeToLiveSeconds:设置Element在失效前允许存活时间。最大时间介于创建时间和失效时间之间。仅当element不是永久有效时使用,默认是0.,也就是element存活时间无穷大
- diskPersistent:是否缓存虚拟机重启期数据
- diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒
- diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区
- memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)
-->
springmvc集成shiro项目的配置:
工程结构:
导入jar包:
![]()
1.web.xml配置:
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>SecurityShiro</display-name> <!-- 执行顺序ServletContext,context-param , listener ,filter ,servlet --> <!-- 指定Spring的配置文件 --> <!-- 否则Spring会默认从WEB-INF下寻找配置文件,contextConfigLocation属性是Spring内部固定的 --> <!-- 通过ContextLoaderListener的父类ContextLoader的第120行发现CONFIG_LOCATION_PARAM固定为contextConfigLocation --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext-shiro.xml</param-value> </context-param> <!-- 防止发生java.beans.Introspector内存泄露,应将它配置在ContextLoaderListener的前面 --> <!-- 详细描述见http://blog.csdn.net/jadyer/article/details/11991457 --> <listener> <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class> </listener> <!-- 实例化Spring容器 --> <!-- 应用启动时,该监听器被执行,它会读取Spring相关配置文件,其默认会到WEB-INF中查找applicationContext.xml --> <!-- http://starscream.iteye.com/blog/1107036 --> <!-- http://www.davenkin.me/post/2012-10-18/40039948363 --> <!-- WebApplicationContextUtils.getWebApplicationContext() --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 解决乱码问题 --> <!-- forceEncoding默认为false,此时效果可大致理解为request.setCharacterEncoding("UTF-8") --> <!-- forceEncoding=true后,可大致理解为request.setCharacterEncoding("UTF-8")和response.setCharacterEncoding("UTF-8") --> <filter> <filter-name>SpringEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>SpringEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 配置Shiro过滤器,先让Shiro过滤系统接收到的请求 --> <!-- 这里filter-name必须对应applicationContext.xml中定义的<bean id="shiroFilter"/> --> <!-- 使用[/*]匹配所有请求,保证所有的可控请求都经过Shiro的过滤 --> <!-- 通常会将此filter-mapping放置到最前面(即其他filter-mapping前面),以保证它是过滤器链中第一个起作用的 --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <!-- 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 --> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- SpringMVC核心分发器 --> <servlet> <servlet-name>SpringMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>SpringMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
2.springmvc.xml配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- 注解驱动 --> <mvc:annotation-driven/> <!-- 自动扫描包 --> <context:component-scan base-package="com" /> <!-- 静态文件防止过滤 --> <!-- <mvc:resources location="res" mapping="/res/**"/> --> <mvc:default-servlet-handler/> <!-- 默认访问跳转到登录页面(即定义无需Controller的url<->view直接映射) --> <mvc:view-controller path="/" view-name="forward:/login.jsp"/> <!-- mvc返回页面的配置 --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- 模板路径为WEB-INF/pages/ --> <property name="prefix"> <value>/</value> </property> <!-- 视图模板后缀为.JSP --> <property name="suffix"> <value>.jsp</value> </property> </bean> </beans>
3.applicationContext-shiro.xml 配置(shiro配置)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.0.xsd"> <!-- 自定义的Realm --> <bean id="myRealm" class="com.authc.shiro.MyRealm"></bean> <!-- Shiro默认会使用Servlet容器的Session,可通过sessionMode属性来指定使用Shiro原生Session --> <!-- 即<property name="sessionMode" value="native"/>,详细说明见官方文档 --> <!-- 这里主要是设置自定义的单Realm应用,若有多个Realm,可使用'realms'属性代替 --> <!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="myRealm"></property> </bean> <!-- Shiro主过滤器本身功能十分强大,其强大之处就在于它支持任何基于URL路径表达式的、自定义的过滤器的执行 --> <!-- Web应用中,Shiro可控制的Web请求必须经过Shiro主过滤器的拦截,Shiro对基于Spring的Web应用提供了完美的支持 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- shiro的安全接口,必须配置的属性 --> <property name="securityManager" ref="securityManager"></property> <!-- 登录时的链接 --> <property name="loginUrl" value="/member/login"></property> <!-- 登录成功后跳转到的链接 --> <!-- <property name="successUrl" value="/member/main"></property> --> <!-- 用户访问未对其授权的资源时,所显示的连接 --> <property name="unauthorizedUrl" value="/member/login"></property> <!-- /error.jsp --> <!-- Shiro连接约束配置,即过滤链的定义 --> <!-- 此处可配合我的这篇文章来理解各个过滤连的作用http://blog.csdn.net/jadyer/article/details/12172839 --> <!-- 下面value值的第一个'/'代表的路径是相对于HttpServletRequest.getContextPath()的值来的 --> <!-- anon:它对应的过滤器里面是空的,什么都没做,这里.do和.jsp后面的*表示参数,比方说login.jsp?main这种 --> <!-- authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter --> <property name="filterChainDefinitions"> <value> /member/login=anon /member/getVerifyCodeImage=anon /member/tologin=anon <!-- 访问 /member/queryMyUserInfo路径需要进行身份认证,并且只有当用户的角色为"member"且权限为"member:delete"时才可以访问该路径 /member/queryMyUserInfo=authc,roles[member],perms[member:delete] --> /member/queryMyUserInfo=authc /admin/**=authc,perms[admin:manage] </value> </property> </bean> <!-- 保证实现了shiro内部Lifecycle函数的bean的执行 (生命周期)--> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"></bean> </beans>
4. MyRealm.java
package com.authc.shiro; import java.util.ArrayList; import java.util.List; import org.apache.commons.lang.builder.ReflectionToStringBuilder; import org.apache.commons.lang.builder.ToStringStyle; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationException; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.session.Session; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.Subject; import com.authc.entity.Permission; import com.authc.entity.Role; import com.authc.entity.User; import com.authc.service.PermissionService; import com.authc.service.RoleService; import com.authc.service.UserService; import com.authc.service.Impl.UserServiceImpl; public class MyRealm extends AuthorizingRealm { private UserService userService =new UserServiceImpl(); /* (non-Javadoc) * @see org.apache.shiro.realm.AuthorizingRealm#doGetAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection) * 为当前登录的Subject授予角色和权限 * @see 经测试:本例中该方法的调用时机为需授权资源被访问时 * @see 经测试:并且每次访问需授权资源时都会执行该方法中的逻辑,这表明本例中默认并未启用AuthorizationCache */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //获取当前登录的用户名 String username =(String) super.getAvailablePrincipal(principals); //根据用户名从数据库中获取用户信息 User user =userService.queryUserByUserName(username); List<String> roleList = new ArrayList<String>(); List<String> permList = new ArrayList<String>(); System.out.println("对当前用户:["+username+"]进行授权!"); if(null!=user) { <!--因为我这里的的role和Permission 只是简单的实现,每个用户只有一个角色以及对于一个权限,所以不需要去遍历,实际工作中需要把用户的多个角色和多个权限都添加到 List<String> roleList和List<String> permList中 --> if(user.getRole()!=null && user.getRole().getRoleName()!=null) { roleList.add(user.getRole().getRoleName()); if(user.getRole().getPermission()!=null && user.getRole().getPermission().getPermName()!=null) { permList.add(user.getRole().getPermission().getPermName()); SimpleAuthorizationInfo info =new SimpleAuthorizationInfo(); info.addRoles(roleList); info.addStringPermissions(permList); return info; } } }else { throw new AuthorizationException(); } //若该方法什么都不做直接返回null的话,就会导致任何用户访问/admin/listUser.jsp时都会自动跳转到unauthorizedUrl指定的地址 return null; } /* (non-Javadoc) * @see org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken) * 验证当前登录的Subject * @see 经测试:本例中该方法的调用时机为LoginController.login()方法中执行Subject.login()时 */ @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken authToken) throws AuthenticationException { UsernamePasswordToken token =(UsernamePasswordToken) authToken; System.out.println("验证当前Subject时获取到token为" + ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE)); User user =userService.queryUserByUserName(token.getUsername()); if(user!=null) { AuthenticationInfo info = new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),getName()); //将当前用户设置到Session中去以便获取当前用户信息 this.setSession("currentUser", user); return info; } return null; } /** * 将一些数据放到ShiroSession中,以便于其它地方使用 * @see 比如Controller,使用时直接用HttpSession.getAttribute(key)就可以取到 */ private void setSession(Object key,Object value) { Subject currSubject =SecurityUtils.getSubject(); if(currSubject!=null) { Session session = currSubject.getSession(); System.out.println("Session默认超时时间为[" + session.getTimeout() + "]毫秒"); if(session!=null) { session.setAttribute(key, value); } } } }
User.java
package com.authc.entity; /** * @author lyx * * 2015-8-26上午8:42:15 * *com.authc.entity.User * */ public class User { private String username; private String password; private Role role; public String getUsername() { return username; } public String getPassword() { return password; } public Role getRole() { return role; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public void setRole(Role role) { this.role = role; } public User(String username, String password, Role role) { super(); this.username = username; this.password = password; this.role = role; } public User() { super(); } }
Role.java
package com.authc.entity; /** * @author lyx * * 2015-8-26上午8:43:15 * *com.authc.entity.Role * */ public class Role { private String roleName; private Permission permission; public String getRoleName() { return roleName; } public Permission getPermission() { return permission; } public void setRoleName(String roleName) { this.roleName = roleName; } public void setPermission(Permission permission) { this.permission = permission; } public Role() { super(); } }
Permission.java
package com.authc.entity; /** * @author lyx * * 2015-8-26上午8:44:26 * *com.authc.entity.Permission * */ public class Permission { private int permId; private String permName; public int getPermId() { return permId; } public String getPermName() { return permName; } public void setPermId(int permId) { this.permId = permId; } public void setPermName(String permName) { this.permName = permName; } public Permission(int permId, String permName) { super(); this.permId = permId; this.permName = permName; } public Permission() { super(); } }
UserService.java
package com.authc.service; import java.util.List; import com.authc.entity.Role; import com.authc.entity.User; /** * @author lyx * * 2015-8-26上午9:17:25 * *com.authc.service.Impl.UserService * */ public interface UserService { public List<User> queryAllUserInfo(); public User queryUserByUserName(String username); public Role queryUserRoleByUsername(String username); }
5.UserServiceImpl.java
package com.authc.service.Impl; import java.util.ArrayList; import java.util.List; import org.springframework.stereotype.Service; import com.authc.entity.Permission; import com.authc.entity.Role; import com.authc.entity.User; import com.authc.service.UserService; public class UserServiceImpl implements UserService { @Override public List<User> queryAllUserInfo() { //没有连接数据库,直接在这里添加数据 Permission perm =new Permission(1,"member:add"); Role role =new Role(); role.setRoleName("member"); role.setPermission(perm); User user=new User(); user.setUsername("liuyuxin"); user.setPassword("111111"); user.setRole(role); Permission perm1 =new Permission(2,"member:delete"); Role role1 =new Role(); role1.setRoleName("member"); role1.setPermission(perm1); User user1=new User(); user1.setUsername("liudong"); user1.setPassword("111111"); user1.setRole(role1); Permission perm2 =new Permission(3,"admin:manage"); Role role2 =new Role(); role2.setRoleName("admin"); role2.setPermission(perm2); User user2=new User(); user2.setUsername("liuhuan"); user2.setPassword("111111"); user2.setRole(role2); List<User> userList = new ArrayList<User>(); userList.add(user); userList.add(user1); userList.add(user2); return userList; } @Override public User queryUserByUserName(String username) { List<User> userList =queryAllUserInfo(); for (User user : userList) { if(user.getUsername().equals(username)) { return user; } } return null; } @Override public Role queryUserRoleByUsername(String username) { List<User> userList =queryAllUserInfo(); Role role =new Role(); for (User user : userList) { if(user.getUsername().equals(username)) { role=user.getRole(); return role; } } return null; } }
6.UserController.java
package com.authc.controller; import java.awt.Color; import java.awt.image.BufferedImage; import java.io.IOException; import javax.imageio.ImageIO; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.commons.lang.StringUtils; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.ExcessiveAttemptsException; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.LockedAccountException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.authz.annotation.RequiresRoles; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.util.WebUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.view.InternalResourceViewResolver; import com.authc.entity.User; import com.authc.service.UserService; import com.authc.utils.SpringUtil; import com.authc.utils.VerifyCodeUtil; import com.google.code.kaptcha.Constants; import com.google.code.kaptcha.Producer; /** * @author lyx * * 2015-8-26上午11:56:03 * *com.authc.controller.UserController * */ @Controller @RequestMapping("/member") public class UserController { //登录 @RequestMapping("/login") public String login() { return "/login"; } /** * VerifyCodeUtil.java 类实现验证码 */ /** * 获取验证码图片和文本(验证码文本会保存在HttpSession中) */ @RequestMapping("/getVerifyCodeImage") public void getVerifyCodeImage(HttpServletRequest request, HttpServletResponse response) throws IOException { //设置页面不缓存 response.setHeader("Pragma", "no-cache"); response.setHeader("Cache-Control", "no-cache"); response.setDateHeader("Expires", 0); String verifyCode = VerifyCodeUtil.generateTextCode(VerifyCodeUtil.TYPE_ALL_MIXED, 4, null); //将验证码放到HttpSession里面 request.getSession().setAttribute("verifyCode", verifyCode); System.out.println("本次生成的验证码为[" + verifyCode + "],已存放到HttpSession中"); //设置输出的内容的类型为JPEG图像 response.setContentType("image/jpeg"); BufferedImage bufferedImage = VerifyCodeUtil.generateImageCode(verifyCode, 90, 25, 4, true, Color.WHITE, Color.BLACK, null); //写给浏览器 ImageIO.write(bufferedImage, "JPEG", response.getOutputStream()); } /** * @param request * @return * 登录验证 */ @RequestMapping("/toLogin") public String toLogin(HttpServletRequest request) { String username = request.getParameter("username"); String password = request.getParameter("password"); //返回地址 String returnUrl="/login"; //获取HttpSession验证码 String verifyCode =(String) request.getSession().getAttribute("verifyCode"); //获取用户输入的验证码 String submitCode = WebUtils.getCleanParam(request,"verifyCode"); System.out.println("用户输入的验证码是:"+submitCode+";系统生成的验证码是:"+verifyCode); if(StringUtils.isEmpty(submitCode)|| !StringUtils.equalsIgnoreCase(verifyCode, submitCode)) { request.setAttribute("login_msg","验证码错误" ); return returnUrl; } //根据获取的用户名和密码封装成Token UsernamePasswordToken token =new UsernamePasswordToken(username,password); //是否记住用户 token.setRememberMe(true); //获取当前的subject Subject subject =SecurityUtils.getSubject(); try { System.out.println("对用户:["+username+"]进行登录验证,验证开始..."); //在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查 //每个Realm都能在必要时对提交的AuthenticationTokens作出反应 //所以这一步在调用login(token)方法时,它会走到MyRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法 subject.login(token); System.out.println("对用户:["+username+"]进行登录验证,验证通过!"); returnUrl="/main"; }catch (UnknownAccountException e) { System.out.println("对用户:["+username+"]进行登录验证,验证未通过!错误:未知账号"); request.setAttribute("login_msg","未知账号"); }catch (IncorrectCredentialsException e) { System.out.println("对用户:["+username+"]进行登录验证,验证未通过!错误:密码错误"); request.setAttribute("login_msg", "密码错误"); }catch (LockedAccountException e) { System.out.println("对用户:["+username+"]进行登录验证,验证未通过!错误:账号被锁定"); request.setAttribute("login_msg", "账号被锁定"); }catch (ExcessiveAttemptsException e) { System.out.println("对用户:["+username+"]进行登录验证,验证未通过!错误:错误次数过多"); request.setAttribute("login_msg", "密码或用户名输入错误次数过多"); }catch (AuthenticationException e) { System.out.println("对用户:["+username+"]进行登录验证,验证未通过!错误:用户名或密码不正确"); request.setAttribute("login_msg", "身份认证失败,用户名或密码不正确"); } //验证是否登录成功 if(subject.isAuthenticated()) { System.out.println("用户:["+username+"]进行登录验证通过"); }else { token.clear(); } return returnUrl; } /** * @param request * @return * 注销登录 */ @RequestMapping("/logout") public String logout(HttpServletRequest request) { SecurityUtils.getSubject().logout(); return "/login"; } @RequestMapping("/queryMyUserInfo") public String queryUserInfo(HttpServletRequest request) { //从Session中取得当前用户信息 User currentUser = (User) request.getSession().getAttribute("currentUser"); request.setAttribute("user", currentUser); return "/myinfo"; } }
7. AdminController.java
package com.authc.controller; import java.util.ArrayList; import java.util.List; import javax.servlet.http.HttpServletRequest; import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.authz.annotation.RequiresRoles; import org.apache.shiro.authz.annotation.RequiresUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseStatus; import com.authc.entity.User; import com.authc.service.PermissionService; import com.authc.service.RoleService; import com.authc.service.UserService; import com.authc.service.Impl.UserServiceImpl; /** * @author lyx * * 2015-8-26上午11:56:45 * *com.authc.controller.AdminController * */ @Controller @RequestMapping("/admin") public class AdminController { private UserService userService =new UserServiceImpl(); @RequestMapping("/queryAllUserInfo") public String queryAllUserInfo(HttpServletRequest request) { List<User> userList = userService.queryAllUserInfo(); request.setAttribute("userList", userList); return "/admin"; } }
8. SpringUtil.java
package com.authc.utils; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @author lyx * * 2015-8-18下午3:53:19 * *com.utils.SpringUtil * TODO */ public class SpringUtil { private static ApplicationContext ctx =new ClassPathXmlApplicationContext("springmvc.xml"); public static Object getBean(String beanId) { return ctx.getBean(beanId); } }
VerifyCodeUtil.java 验证码
package com.authc.utils; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.image.BufferedImage; import java.util.Random; /** * 验证码生成器 * @see -------------------------------------------------------------------------------------------------------------- * @see 可生成数字、大写、小写字母及三者混合类型的验证码 * @see 支持自定义验证码字符数量,支持自定义验证码图片的大小,支持自定义需排除的特殊字符,支持自定义干扰线的数量,支持自定义验证码图文颜色 * @see -------------------------------------------------------------------------------------------------------------- * @see 另外,给Shiro加入验证码有多种方式,也可以通过继承修改FormAuthenticationFilter类,通过Shiro去验证验证码 * @see 而这里既然使用了SpringMVC,也为了简化操作,就使用此工具生成验证码,并在Controller中处理验证码的校验 * @see -------------------------------------------------------------------------------------------------------------- * @create Sep 29, 2013 4:23:13 PM * @author 玄玉<http://blog.csdn.net/jadyer> */ public class VerifyCodeUtil { /** * 验证码类型为仅数字,即0~9 */ public static final int TYPE_NUM_ONLY = 0; /** * 验证码类型为仅字母,即大小写字母混合 */ public static final int TYPE_LETTER_ONLY = 1; /** * 验证码类型为数字和大小写字母混合 */ public static final int TYPE_ALL_MIXED = 2; /** * 验证码类型为数字和大写字母混合 */ public static final int TYPE_NUM_UPPER = 3; /** * 验证码类型为数字和小写字母混合 */ public static final int TYPE_NUM_LOWER = 4; /** * 验证码类型为仅大写字母 */ public static final int TYPE_UPPER_ONLY = 5; /** * 验证码类型为仅小写字母 */ public static final int TYPE_LOWER_ONLY = 6; private VerifyCodeUtil(){} /** * 生成随机颜色 */ private static Color generateRandomColor() { Random random = new Random(); return new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)); } /** * 生成图片验证码 * @param type 验证码类型,参见本类的静态属性 * @param length 验证码字符长度,要求大于0的整数 * @param excludeString 需排除的特殊字符 * @param width 图片宽度(注意此宽度若过小,容易造成验证码文本显示不全,如4个字符的文本可使用85到90的宽度) * @param height 图片高度 * @param interLine 图片中干扰线的条数 * @param randomLocation 每个字符的高低位置是否随机 * @param backColor 图片颜色,若为null则表示采用随机颜色 * @param foreColor 字体颜色,若为null则表示采用随机颜色 * @param lineColor 干扰线颜色,若为null则表示采用随机颜色 * @return 图片缓存对象 */ public static BufferedImage generateImageCode(int type, int length, String excludeString, int width, int height, int interLine, boolean randomLocation, Color backColor, Color foreColor, Color lineColor){ String textCode = generateTextCode(type, length, excludeString); return generateImageCode(textCode, width, height, interLine, randomLocation, backColor, foreColor, lineColor); } /** * 生成验证码字符串 * @param type 验证码类型,参见本类的静态属性 * @param length 验证码长度,要求大于0的整数 * @param excludeString 需排除的特殊字符(无需排除则为null) * @return 验证码字符串 */ public static String generateTextCode(int type, int length, String excludeString){ if(length <= 0){ return ""; } StringBuffer verifyCode = new StringBuffer(); int i = 0; Random random = new Random(); switch(type){ case TYPE_NUM_ONLY: while(i < length){ int t = random.nextInt(10); //排除特殊字符 if(null==excludeString || excludeString.indexOf(t+"")<0) { verifyCode.append(t); i++; } } break; case TYPE_LETTER_ONLY: while(i < length){ int t = random.nextInt(123); if((t>=97 || (t>=65&&t<=90)) && (null==excludeString||excludeString.indexOf((char)t)<0)){ verifyCode.append((char)t); i++; } } break; case TYPE_ALL_MIXED: while(i < length){ int t = random.nextInt(123); if((t>=97 || (t>=65&&t<=90) || (t>=48&&t<=57)) && (null==excludeString||excludeString.indexOf((char)t)<0)){ verifyCode.append((char)t); i++; } } break; case TYPE_NUM_UPPER: while(i < length){ int t = random.nextInt(91); if((t>=65 || (t>=48&&t<=57)) && (null==excludeString || excludeString.indexOf((char)t)<0)){ verifyCode.append((char)t); i++; } } break; case TYPE_NUM_LOWER: while(i < length){ int t = random.nextInt(123); if((t>=97 || (t>=48&&t<=57)) && (null==excludeString || excludeString.indexOf((char)t)<0)){ verifyCode.append((char)t); i++; } } break; case TYPE_UPPER_ONLY: while(i < length){ int t = random.nextInt(91); if((t >= 65) && (null==excludeString||excludeString.indexOf((char)t)<0)){ verifyCode.append((char)t); i++; } } break; case TYPE_LOWER_ONLY: while(i < length){ int t = random.nextInt(123); if((t>=97) && (null==excludeString||excludeString.indexOf((char)t)<0)){ verifyCode.append((char)t); i++; } } break; } return verifyCode.toString(); } /** * 已有验证码,生成验证码图片 * @param textCode 文本验证码 * @param width 图片宽度(注意此宽度若过小,容易造成验证码文本显示不全,如4个字符的文本可使用85到90的宽度) * @param height 图片高度 * @param interLine 图片中干扰线的条数 * @param randomLocation 每个字符的高低位置是否随机 * @param backColor 图片颜色,若为null则表示采用随机颜色 * @param foreColor 字体颜色,若为null则表示采用随机颜色 * @param lineColor 干扰线颜色,若为null则表示采用随机颜色 * @return 图片缓存对象 */ public static BufferedImage generateImageCode(String textCode, int width, int height, int interLine, boolean randomLocation, Color backColor, Color foreColor, Color lineColor){ //创建内存图像 BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); //获取图形上下文 Graphics graphics = bufferedImage.getGraphics(); //画背景图 graphics.setColor(null==backColor ? generateRandomColor() : backColor); graphics.fillRect(0, 0, width, height); //画干扰线 Random random = new Random(); if(interLine > 0){ int x = 0, y = 0, x1 = width, y1 = 0; for(int i=0; i<interLine; i++){ graphics.setColor(null==lineColor ? generateRandomColor() : lineColor); y = random.nextInt(height); y1 = random.nextInt(height); graphics.drawLine(x, y, x1, y1); } } //字体大小为图片高度的80% int fsize = (int)(height * 0.8); int fx = height - fsize; int fy = fsize; //设定字体 graphics.setFont(new Font("Default", Font.PLAIN, fsize)); //写验证码字符 for(int i=0; i<textCode.length(); i++){ fy = randomLocation ? (int)((Math.random()*0.3+0.6)*height) : fy; graphics.setColor(null==foreColor ? generateRandomColor() : foreColor); //将验证码字符显示到图象中 graphics.drawString(textCode.charAt(i)+"", fx, fy); fx += fsize * 0.9; } graphics.dispose(); return bufferedImage; } }
9.login.jsp登录页面
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@page isELIgnored="false"%> <% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; request.setAttribute("home", path); %> <!DOCTYPE HTML> <html lang="en-US"> <!-- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> --> <script type="text/javascript" src="<%=request.getContextPath()%>/res/login/prefixfree.min.js"></script> <script type="text/javascript" src="<%=request.getContextPath()%>/res/js/jquery-1.11.3.min.js"></script> <head> <meta charset="UTF-8"> <title>用户登录</title> <link rel="stylesheet" href="<%=request.getContextPath()%>/res/login/login.css" type="text/css"></link> <script type="text/javascript"> var home ="${home}"; var msg ="${login_msg }"; $(function(){ //生成验证码 $('#verifyCodeImage').click(function () { $(this).hide().attr('src', '<%=path%>/member/getVerifyCodeImage?' + Math.floor(Math.random()*100) ).fadeIn(); }); }); window.onbeforeunload = function(){ //关闭窗口时自动退出 if(event.clientX>360&&event.clientY<0||event.altKey){ alert(parent.document.location); } }; function changeCode() { //刷新 $('#verifyCodeImage').hide().attr('src', '<%=path%>/member/getVerifyCodeImage?' + Math.floor(Math.random()*100) ).fadeIn(); event.cancelBubble=true; } if(msg!="") { alert(msg); } </script> </head> <body> <div class="content"> <form action="<%=request.getContextPath()%>/member/toLogin" method="post" class="login-form"> <div class="username"> <input type="text" name="username" placeholder="emma.watson@gmail.com" autocomplete="on" /> <div id="loginMsg"></div> <span class="user-icon icon">u</span> </div> <div class="password"> <input type="password" name="password" placeholder="*******" /> <span class="password-icon icon">p</span> </div> <div class="code-div"> <input type="text" name="verifyCode" placeholder="请输入验证码" /> <img id="verifyCodeImage" src="<%=request.getContextPath()%>/member/getVerifyCodeImage"/> <!-- <a href="javascript:void(0)" οnclick="changeCode()">看不清?换一张</a> --> </div> <div class="account-control"> <input type="checkbox" name="rememberMe" id="Remember me" value="Remember me" checked="checked" /> <label for="Remember me" data-on="c" class="check"></label> <label for="Remember me" class="info">Remember me</label> <!-- <input type="hidden" name="rememberMe" value="true"> --> <button type="submit">Login</button> </div> <p class="not-registered">Not a registered user yet?<a>Sign up now!</a></p> </form> </div> </body> </html>
10.main.jsp主界面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="utf-8"%> <%@page isELIgnored="false"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title> 功能界面</title> </head> <body> <ul> <li> 普通用户可访问<a href="<%=request.getContextPath()%>/member/queryMyUserInfo" target="_blank">用户信息页面</a> </li> <li> 管理员可访问<a href="<%=request.getContextPath()%>/admin/queryAllUserInfo" target="_blank">用户列表页面</a> </li> <li> <a href="<%=request.getContextPath()%>/member/logout" target="_blank">Logout</a> </li> </ul> </body> </html>
login登陆页面: