原理回顾

什么是权限管理?
权限管理是系统的安全范畴,要求必须是合法的用户才可以访问系统(用户认证),且必须具有该 资源的访问权限才可以访问该 资源(授权)。
认证:对用户合法身份的校验,要求必须是合法的用户才可以访问系统。
授权:访问控制,必须具有该 资源的访问权限才可以访问该 资源。
权限模型:标准权限数据模型包括 :用户、角色、权限(包括资源和权限)、用户角色关系、角色权限关系。
权限分配:通过UI界面方便给用户分配权限,对上边权限模型进行增、删、改、查操作。
权限控制:

  • 基于角色的权限控制:根据角色判断是否有操作权限,因为角色的变化 性较高,如果角色修改需要修改控制代码,系统可扩展性不强。
  • 基于资源的权限控制:根据资源权限判断是否有操作权限,因为资源较为固定,如果角色修改或角色中权限修改不需要修改控制代码,使用此方法系统可维护性很强。建议使用。

权限管理的解决方案:

  • 对于粗颗粒权限管理,建议在系统架构层面去解决,写系统架构级别统一代码(基础代码)。所谓粗颗粒权限,比如对系统的url、菜单、jsp页面、页面上按钮、类方法进行权限管理,即对资源类型进行权限管理。
  • 对于细颗粒权限管理:所谓细颗粒权限, 比如用户id为001的用户信息(资源实例)、类型为t01的商品信息(资源实例),对资源实例进行权限管理,理解对数据级别的权限管理。细颗粒权限管理是系统的业务逻辑,业务逻辑代码不方便抽取统一代码,建议在系统业务层进行处理。

基于url的权限管理(掌握):

  • 企业开发常用的方法,使用web应用中filter来实现,用户请求url,通过filter拦截,判断用户身份是否合法(用户认证),判断请求的地址是否是用户权限范围内的url(授权)。

shiro:shiro是一个权限管理框架,是apache下的开源项目。相比spring security框架更简单灵活,spring security对spring依赖较强。shiro可以实现web系统、c/s、分布式等系统 权限管理。
shiro认证流程:(掌握)

  • 1.subject(主体)请求认证,调用subject.login(token)
  • 2.SecurityManager (安全管理器)执行认证
  • 3.SecurityManager通过ModularRealmAuthenticator进行认证。
  • 4.ModularRealmAuthenticator将token传给realm,realm根据token中用户信息从数据库查询用户信息(包括身份和凭证)
  • 5.realm如果查询不到用户给ModularRealmAuthenticator返回null,ModularRealmAuthenticator抛出异常(用户不存在)
  • 6.realm如果查询到用户给ModularRealmAuthenticator返回AuthenticationInfo(认证信息)
  • 7.ModularRealmAuthenticator拿着AuthenticationInfo(认证信息)去进行凭证(密码 )比对。如果一致则认证通过,如果不致抛出异常(凭证错误)。

subject:主体
Authenticator:认证器( shiro提供)
realm(一般需要自定义):相当于数据源,认证器需要realm从数据源查询用户身份信息及权限信息。

shiro与项目集成开发

shiro与spring web项目整合

shiro与spring web项目整合在”基于url拦截实现的工程” 基础上整合,基于url拦截实现的工程的技术架构师SpringMVC+Mybatis,整合时注意两点:
1.shiro与Spring整合
2.加入shiro对web应用的支持

创建web工程

创建新工程permission_web_shiro

取消原springmvc认证和授权拦截器

去除springmvc.xml中配置的LoginInterceptor和PermissionInterceptor拦截器

导入shiro相应的jar包

主要包括:
shiro-web:shiro整合web项目必须的包
shiro-spring:shiro整合Spring项目需要的整合包,依赖于shiro-web包
shiro-core:shiro核心包。必须导入的包。

JAVAWEB开发之权限管理(三)——shiro与企业项目整合开发(基于Spring) - 图1

在web.xml中配置shiro的filter

在web系统中,shiro也通过filter进行拦截,filter拦截器后将操作权交给Spring中配置的filterChain(过滤器链),shiro提供很多filter。要使用代理filter类DelegatingFilterProxy
JAVAWEB开发之权限管理(三)——shiro与企业项目整合开发(基于Spring) - 图2

  1. 1. <!-- shirofilter -->
  2. 2. <!-- shiro过滤器,DelegatingFilterProxy通过代理模式将Spring容器中的beanfilter关联起来 -->
  3. 3. <filter>
  4. 4. <filter-name>shiroFilter</filter-name>
  5. 5. <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  6. 6. <!-- 设置targetFilterLifecycletrue servlet控制filter生命周期 -->
  7. 7. <init-param>
  8. 8. <param-name>targetFilterLifecycle</param-name>
  9. 9. <param-value>true</param-value>
  10. 10. </init-param>
  11. 11. <!-- 设置Spring容器filterbean id,如果不设置则在Spring注册的bean中查找与filter-name一致的bean -->
  12. 12. <init-param>
  13. 13. <param-name>targetBeanName</param-name>
  14. 14. <param-value>shiroFilter</param-value>
  15. 15. </init-param>
  16. 16. </filter>
  17. 17. <filter-mapping>
  18. 18. <filter-name>shiroFilter</filter-name>
  19. 19. <url-pattern>/*</url-pattern>
  20. 20. </filter-mapping>

applicationContext-shiro.xml

1. <!-- web.xml中shiro的filter对应的bean -->
2.     <!-- Shiro的web过滤器 -->
3.     <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
4.         <property name="securityManager" ref="securityManager"/>
5.         <!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求此地址将由formAuthenticationFilter进行表单认证 -->
6.         <property name="loginUrl" value="/login.action"/>
7.         <!-- 认证成功统一跳转到first.action,建议不配置,默认情况下,shiro认证成功后自动跳转上一个请求路径 -->
8.         <property name="successUrl" value="/first.action"/>
9.         <!-- 通过unauthorizedUrl 指定没有权限操作时的跳转页面 -->
10.         <property name="unauthorizedUrl" value="/refuse.jsp"/>
11.         <!-- 过滤器链定义,从上向下执行,一般将/**放在最下边 -->
12.         <property name="filterChainDefinitions">
13.             <value>
14.                 <!-- 退出拦截,请求logout.action执行退出操作 shiro自动清除Session-->
15.                 /logout.action = logout
16.                 <!-- 无权访问页面 anon表示可以匿名访问 -->
17.                 /refuse.jsp = anon
18.                 <!-- 验证码可以匿名访问 -->
19.                 /validatecode.jsp = anon
20.                 <!-- perms[xx] 表示有xx权限才可以访问 -->
21.                 /item/queryItem.action = perms[item:query]
22.                 /item/editItem.action = perms[item:edit]
23.                 <!-- 对静态资源设置匿名访问 -->
24.                 /js/** = anon
25.                 /images/** = anon
26.                 /styles/** = anon
27. 
28.                 <!-- /** = authc 表示所有的URL都必须认真通过才可以进行访问-->
29.                 /** = authc    
30.             </value>        
31.         </property>
32.     </bean>
33. 
34.     <!-- 安全管理器 -->
35.     <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
36.         <property name="realm" ref="customRealm" />
37.     </bean>
38. 
39.     <!-- 自定义Realm -->
40.     <bean id="customRealm" class="liuxun.ssm.shiro.CustomRealm"/>

securityManager:这个属性是必须的
loginUrl:没有登录认证的用户请求将跳转到此地址进行认证,不是必须的属性,不输入地址的话会自动寻找web项目的根目录下的”login.jsp” 页面

自定义Realm模拟测试

此Realm先不从数据库查询权限数据,当前需要先将shiro整合完成。

1. package liuxun.ssm.shiro;
2. 
3. import java.util.ArrayList;
4. import java.util.List;
5. 
6. import org.apache.shiro.authc.AuthenticationException;
7. import org.apache.shiro.authc.AuthenticationInfo;
8. import org.apache.shiro.authc.AuthenticationToken;
9. import org.apache.shiro.authc.SimpleAuthenticationInfo;
10. import org.apache.shiro.authc.UsernamePasswordToken;
11. import org.apache.shiro.authz.AuthorizationInfo;
12. import org.apache.shiro.authz.SimpleAuthorizationInfo;
13. import org.apache.shiro.realm.AuthorizingRealm;
14. import org.apache.shiro.subject.PrincipalCollection;
15. 
16. import liuxun.ssm.po.ActiveUser;
17. import liuxun.ssm.po.SysPermission;
18. 
19. public class CustomRealm extends AuthorizingRealm {
20. 
21.     // 设置Realm名称
22.     @Override
23.     public void setName(String name) {
24.         super.setName("CustomRealm");
25.     }
26. 
27.     // 支持UsernamePasswordToken
28.     @Override
29.     public boolean supports(AuthenticationToken token) {
30.         return token instanceof UsernamePasswordToken;
31.     }
32. 
33.     // 用于认证
34.     @Override
35.     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
36.         // 从token中获取用户身份信息
37.         String username = (String) token.getPrincipal();
38.         // 拿着username从数据库中进行查询
39.         // ....
40.         // 如果查询不到返回null
41.         if (!username.equals("zhangsan")) {
42.             return null;
43.         }
44. 
45.         // 获取从数据库查询出来的用户密码
46.         String password = "123"; // 这里使用静态数据进行测试
47. 
48.         // 根据用户id从数据库中取出菜单
49.         // ...先使用静态数据
50.         List<SysPermission> menus = new ArrayList<SysPermission>();
51.         SysPermission sysPermission_1 = new SysPermission();
52.         sysPermission_1.setName("商品管理");
53.         sysPermission_1.setUrl("/item/queryItem.action");
54.         SysPermission sysPermission_2 = new SysPermission();
55.         sysPermission_2.setName("用户管理");
56.         sysPermission_2.setUrl("/user/query.action");
57. 
58.         menus.add(sysPermission_1);
59.         menus.add(sysPermission_2);
60. 
61.         // 构建用户身份信息
62.         ActiveUser activeUser = new ActiveUser();
63.         activeUser.setUserid(username);
64.         activeUser.setUsername(username);
65.         activeUser.setUsercode(username);
66.         activeUser.setMenus(menus);
67. 
68.         // 返回认证信息由父类AuthenticationRealm进行认证
69.         SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(activeUser, password,
70.                 this.getName());
71. 
72.         return simpleAuthenticationInfo;
73.     }
74. 
75.     // 用于授权
76.     @Override
77.     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
78.         //获取身份信息
79.         ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();
80.         //用户id
81.         String userid = activeUser.getUserid();
82.         // 根据用户id从数据库中查询权限数据
83.         // ...这里使用静态数据模拟
84.         List<String> permissions = new ArrayList<String>();
85.         permissions.add("item:query");
86.         permissions.add("item:update");
87. 
88.         //将权限信息封装为AuthorizationInfo
89.         SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
90.         //基于资源权限的访问控制
91.         for (String permission : permissions) {
92.             simpleAuthorizationInfo.addStringPermission(permission);
93.         }
94.         // 如果基于角色进行访问控制
95.         // for (String role : roles) {
96.         // simpleAuthorizationInfo.addRole(role);
97.         // }
98. 
99.         return simpleAuthorizationInfo;
100.     }
101. 
102. }

登录

(1) 登录原理
使用FormAuthenticationFilter过滤器实现,原理如下:
当用户没有认证时,请求loginUrl进行认证,用户身份和用户密码提交数据到loginUrl,FormAuthorizationFilter拦截去除request中的username和password(两个参数名称是可以配置的),FormAuthorizationFilter调用Realm传入一个token(username和password),realm认证时根据username查询用户信息(在ActiveUser中存储,包括userid、usercode、username、menus) 如果查询不到,Realm返回null,FormAuthorizationFilter向request域中填充了一个参数”shiroLoginFailure” 记录了异常信息。
(2) 登录页面
由于FormAuthorizationFilter的用户身份和密码的input的默认值(username和password),修改页面中账号和密码的input的name属性为username和password。
(3)控制器登录代码的实现,如下:

1. //用户登录提交方法
2.     @RequestMapping("/login")
3.     public String login(HttpServletRequest request) throws Exception{
4. 
5.         //shiro在认证通过后出现错误后将异常类路径通过request返回
6.         //如果登陆失败从request中获取认证异常信息,shiroLoginFailure就是shiro异常类的全限定名
7.         String exceptionClassName = (String)request.getAttribute("shiroLoginFailure");
8.         if (exceptionClassName!=null) {
9.             if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
10.                 throw new CustomException("账号不存在");
11.             } else if(IncorrectCredentialsException.class.getName().equals(exceptionClassName)){
12.                 throw new CustomException("用户名/密码错误");
13.             }else {
14.                 throw new Exception(); //最终在设置的异常处理器中生成未知错误
15.             }
16.         }
17.         //此方法不处理登录成功(认证成功)的情况
18.         //如果登录失败还到login页面
19.         return "login";
20.     }

首页

1.认证成功后用户菜单在首页显示(从activeUser获取)
2.认证后用户的信息在页头显示(从activeUser获取)
由于session由shiro管理,需要修改首页的controller方法,将session中的数据通过model传到页面

1. //系统首页
2.     @RequestMapping("/first")
3.     public String first(Model model)throws Exception{
4.         //主体
5.         Subject subject = SecurityUtils.getSubject();
6.         //身份
7.         ActiveUser activeUser = (ActiveUser) subject.getPrincipal();
8.         model.addAttribute("activeUser", activeUser);
9.         return "/first";
10.     }

退出

由于使用shiro的sessionManager,不用开发退出功能,使用shiro的logout拦截器即可。也就是说不用我们去实现退出,只要去访问一个退出的url(该 url是可以不存在),由LogoutFilter拦截住,清除session。所以可以屏蔽退出相关的控制器代码。


/logout.action = logout
### 无权限页面refuse.jsp
当用户无操作权限,shiro将跳转到refuse.jsp页面。

### 授权过滤器测试 使用PermissionsAuthorizationFilter
在applicationContext-shiro.xml中配置url所对应的权限。
测试流程:
1、在applicationContext-shiro.xml中配置filter规则

/item/queryItem.action = perms[item:query]
2、用户在认证通过后,请求/items/queryItems.action
3、被PermissionsAuthorizationFilter拦截,发现需要“item:query”权限
4、PermissionsAuthorizationFilter调用realm中的doGetAuthorizationInfo获取数据库中正确的权限
5、PermissionsAuthorizationFilter对item:query 和从realm中获取权限进行对比,如果“item:query”在realm返回的权限列表中,授权通过。如果授权失败,跳转到refuse.jsp 页面如下:
JAVAWEB开发之权限管理(三)——shiro与企业项目整合开发(基于Spring) - 图3 点击修改
JAVAWEB开发之权限管理(三)——shiro与企业项目整合开发(基于Spring) - 图4
### 问题总结 1、在applicationContext-shiro.xml中配置过虑器链接,需要将全部的url和权限对应起来进行配置,比较发麻不方便使用。
2、每次授权都需要调用realm查询数据库,对于系统性能有很大影响,可以通过shiro缓存来解决。
### shiro过滤器总结 过滤器简称 对应的java类
anon org.apache.shiro.web.filter.authc.AnonymousFilter
authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port org.apache.shiro.web.filter.authz.PortFilter
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl org.apache.shiro.web.filter.authz.SslFilter
user org.apache.shiro.web.filter.authc.UserFilter
logout org.apache.shiro.web.filter.authc.LogoutFilter 过滤器配置示例详解:
anon:例如/admins/=anon 没有参数,表示可以匿名访问。
authc:例如/admins/user/
=authc 表示需要认证(登录)才能使用,FormAuthenticationFilter是表单认证,没有参数。
roles:例如/admins/user/=roles[admin],参数可以写多个,多个时必须加引号,并且参数之间用逗号隔开,,当有多个参数时,例如/admin/user/=roles[“admin,geust”],每个参数通过才算通过,相当于hasAllRoles()方法。
perms:例如/admins/user/=perms[user:add:*],参数可以写多个,多个时必须加引号,并且参数之间用逗号分隔,例如/admins/user/=perms[“user:add:,user:modiffy:“],当有多个参数时必须每个参数都通过才算通过,相当于isPermittedAll()方法。
rest:例如/admins/user/=rest[user],根据请求的方法,相当于/admins/user/=perms[user:method],其中method为post,get,put,delete等 rest 即restful,就是用于对restful风格的设计进行权限管理。
port:例子/admins/user/=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString是你访问的url里的?后面的参数。 authcBasic:例如/admins/user/=authcBasic没有参数表示httpBasic认证 ssl:例子/admins/user/=ssl没有参数,表示安全的url请求,协议为https user:例如/admins/user/=user没有参数表示必须存在用户, 身份认证通过或通过记住我认证通过的可以访问,当登入操作时不做检查 注意:anon、authcBasic、authc、user是认证过滤器,perms、roles、ssl、rest、port是授权过滤器
URL表达式说明:
(1)URL目录是基于HttpServletRequest.getContextPath()此目录设置。
(2)URL可以使用通配符,** 代表任意子目录
(3)shiro验证URL时,URL匹配成功便不再不再继续匹配查找。所以要注意配置文件中的URL顺序,尤其是在使用通配符时。
过滤器链定义说明:
(1)一个URL可以配置多个Filter,使用逗号隔开。
(2)当设置多个过滤器时,全部验证通过,才视为通过。
(3)部分过滤器可以指定参数,如perms、roles ## 认证 ### 需求 修改realm的doGetAuthenticationInfo方法,从数据库中查询用户,realm返回的用户信息中包括(MD5加密后的串和salt),实现让shiro进行散列串的校验。 ### 添加凭证匹配器 添加凭证匹配器实现MD5加密校验,修改applicationContext-shiro.xml 修改内容如下: ``` 1.
  1. <a name="5Ob44"></a>
    ### 修改realm的认证方法
    修改realm代码从数据库中查询用户身份信息,将sysService注入realm。
    
  2. public class CustomRealm extends AuthorizingRealm {
  3. @Autowired
  4. private SysService sysService;
  5. // 设置Realm名称
  6. @Override
  7. public void setName(String name) {
  8. super.setName(“CustomRealm”);
  9. }
  10. // 支持UsernamePasswordToken
  11. @Override
  12. public boolean supports(AuthenticationToken token) {
  13. return token instanceof UsernamePasswordToken;
  14. }
  15. // 用于认证(从数据库中查询用户信息)
  16. @Override
  17. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
  18. // 从token中获取用户身份信息
  19. String userCode = (String) token.getPrincipal();
  20. SysUser sysUser = null;
  21. try {
  22. sysUser = sysService.findSysUserByUserCode(userCode);
  23. } catch (Exception e) {
  24. e.printStackTrace();
  25. }
  26. //如果账号不存在则返回null
  27. if (sysUser == null) {
  28. return null;
  29. }
  30. //根据用户id取出菜单
  31. List menus = null;
  32. try {
  33. menus = sysService.findMenuListByUserId(sysUser.getId());
  34. } catch (Exception e) {
  35. e.printStackTrace();
  36. }
  37. //用户密码
  38. String password = sysUser.getPassword();
  39. //盐
  40. String salt = sysUser.getSalt();
  41. //构建用户身份信息
  42. ActiveUser activeUser = new ActiveUser();
  43. activeUser.setUserid(sysUser.getId());
  44. activeUser.setUsername(sysUser.getUsername());
  45. activeUser.setUsercode(sysUser.getUsercode());
  46. activeUser.setMenus(menus);
  47. //将activeUser设置simpleAuthenticationInfo
  48. SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(activeUser, password,
  49. ByteSource.Util.bytes(salt), this.getName());
  50. return simpleAuthenticationInfo;
  51. }
  52. ……
  53. }
    <a name="w8wSs"></a>
    ## 授权
    <a name="wfhlz"></a>
    ### 修改realm授权方法
    修改realm代码中的doGetAuthorizationInfo方法代码从数据库中查询权限信息(已经注入sysService)
    
  54. // 用于授权(从数据库中查询授权信息)
  55. @Override
  56. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  57. //获取身份信息
  58. ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();
  59. //用户id
  60. String userid = activeUser.getUserid();
  61. //获取用户权限
  62. List permissionsList = null;
  63. try {
  64. permissionsList = sysService.findPermissionListByUserId(userid);
  65. } catch (Exception e) {
  66. e.printStackTrace();
  67. }
  68. //构建shiro授权信息
  69. SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
  70. //单独定义一个集合
  71. List permissions = new ArrayList();
  72. for (SysPermission sysPermission : permissionsList) {
  73. //将数据库中的权限标签放入集合
  74. permissions.add(sysPermission.getPercode());
  75. }
  76. simpleAuthorizationInfo.addStringPermissions(permissions);
  77. return simpleAuthorizationInfo;
  78. }
    <a name="QA694"></a>
    ### 对controller开启AOP
    在springmvc.xml中配置shiro注解支持,可在controller方法中使用shiro注解配置权限
    
  79. <a name="xZ5Ut"></a>
    ### 权限注解控制
    商品查询的controller方法添加权限(item:query)
    
  80. // 查询商品列表信息
  81. @RequestMapping(“/queryItem”)
  82. @RequiresPermissions(“item:query”)
  83. public ModelAndView queryItems(HttpServletRequest request) throws Exception {
    上边代码中@RequiresPermissions("item:query")表示必须具有"item:query"权限方可执行。<br />同理,商品修改controller方法添加权限(item:update)
    
  84. // 方法返回字符串,字符串就是逻辑视图名,Model作用就是将数据填充到request域,在页面展示
  85. @RequestMapping(value=”/editItem”,method={RequestMethod.GET})
  86. @RequiresPermissions(“item:update”)
  87. public String editItems(Model model,Integer id) throws Exception{
    商品修改提交
    
  88. // 商品修改提交
  89. // itemsQueryVo是包装类型的pojo
  90. @RequestMapping(“/editItemSubmit”)
  91. @RequiresPermissions(“item:update”)
  92. //注意:每个校验pojo的前边必须加@Validated, 每个校验的pojo后边必须加BindingResult接收错误信息
  93. public String editItemSubmit(Model model,Integer id,
  94. @Validated(value={ValidGroup1.class}) @ModelAttribute(value=”item”)ItemsCustom itemsCustom,
  95. BindingResult bindingResult,
  96. // 上传图片
  97. MultipartFile pictureFile
  98. ) throws Exception {
    <a name="hcXhY"></a>
    ### JSP标签控制
    <a name="spGt7"></a>
    #### shiro标签介绍
    Jsp页面添加: <br /><%@ tagliburi="http://shiro.apache.org/tags" prefix="shiro" %> <br />标签名称  标签条件(均是显示标签内容) <br /><shiro:authenticated>          登录之后 <br /><shiro:notAuthenticated>    不在登录状态时 <br /><shiro:guest>                       用户在没有RememberMe时 <br /><shiro:user>                         用户在RememberMe时 <br /><shiro:hasAnyRoles name="abc,123" >    在有abc或者123角色时 <br /><shiro:hasRole name="abc">                    拥有角色abc <br /><shiro:lacksRole name="abc">                  没有角色abc <br /><shiro:hasPermission name="abc">          拥有权限资源abc <br /><shiro:lacksPermission name="abc">        没有abc权限资源 <br /><shiro:principal>                                         显示用户身份名称 <br /><shiro:principal property="username"/>     显示用户身份中的属性值 
    <a name="pWsEo"></a>
    #### jsp页面添加标签
    如果有商品修改权限 页面显示"修改链接"
    
  99. 修改
  100. <a name="7XN7B"></a>
    ### 授权测试
    (1) 当调用controller的一个方法,由于该 方法加了@RequiresPermissions("item:query") ,shiro调用realm获取数据库中的权限信息,看"item:query"是否在权限数据中存在,如果不存在就拒绝访问,如果存在就授权通过。 <br />(2) 当展示一个jsp页面时,页面中如果遇到<shiro:hasPermission name="item:update">,shiro调用realm获取数据库中的权限信息,看item:update是否在权限数据中存在,如果不存在就拒绝访问,如果存在就授权通过。 <br />问题:只要遇到注解或jsp标签的授权,都会调用realm方法查询数据库,需要使用缓存解决此问题。 
    <a name="F3Ffp"></a>
    ## shiro缓存
    shiro每次授权都会通过realm获取权限信息,为了提高访问速度需要添加缓存,第一次从realm中读取权限数据,之后不再读取,这里Shiro和Ehcache整合。 
    <a name="tBzwN"></a>
    ### 添加Ehcache的jar包
    ![](https://cdn.nlark.com/yuque/0/2021/png/12851533/1618993113555-ef381cbe-5557-4b73-91ff-d18677c1ddb2.png#align=left&display=inline&height=92&margin=%5Bobject%20Object%5D&originHeight=92&originWidth=726&size=0&status=done&style=none&width=726)
    <a name="nGC9B"></a>
    ### 配置cacheManager
    在applicationContext-shiro.xml中配置缓存管理器。
    
  101. <a name="ZsjwN"></a>
    ### 配置shiro-ehcache.xml
    
  102. <ehcache xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance
  103. xsi:noNamespaceSchemaLocation=”../config/ehcache.xsd”>
  104. <defaultCache
  105. maxElementsInMemory=”1000”
  106. maxElementsOnDisk=”10000000”
  107. eternal=”false”
  108. overflowToDisk=”false”
  109. diskPersistent=”false”
  110. timeToIdleSeconds=”120”
  111. timeToLiveSeconds=”120”
  112. diskExpiryThreadIntervalSeconds=”120”
  113. memoryStoreEvictionPolicy=”LRU”>
  114. <a name="8yIzD"></a>
    ### 清空缓存
    如果用户正常退出,缓存自动清空。如果用户非正常退出,缓存自动清空。 <br />如果修改了用户的权限,而用户不退出系统,旧的权限数据缓存在服务器,读取仍先从缓存获取权限数据,修改的权限无法立即生效。 <br />需要手动进行编程实现: <br />在权限修改后调用realm的clearCache方法清除缓存。 <br />下边的代码正常开发时要放在service中调用。 <br />在service中,权限修改后调用realm的方法。 <br />在自定义的Realm中定义clearCached方法
    
  115. //清空缓存
  116. public void clearCached(){
  117. //清空所有用户的身份缓存信息
  118. PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
  119. super.clearCache(principals);
  120. }
    在权限修改后调用realm中的方法,realm已经由spring管理,所以从spring中获取realm实例,调用clearCached方法。 <br />测试清除缓存controller方法
    
  121. @Controller
  122. public class ClearShiroCache {
  123. //注入realm
  124. @Autowired
  125. private CustomRealm customRealm;
  126. @RequestMapping(“/clearShiroCache”)
  127. public String clearShiroCache(){
  128. //清除缓存,如果按照标准写法是在Service中调用customRealm.clearCached();
  129. customRealm.clearCached();
  130. return “success”;
  131. }
  132. } 15.
    <a name="xr4iv"></a>
    ## session管理
    在applicationContext-shiro.xml中配置sessionManager,修改的关键代码如下:
    
  133. <a name="q5HdN"></a>
    ## 验证码
    <a name="sNd96"></a>
    ### 思路
    shiro使用FormAuthenticationFilter进行表单认证,验证校验的功能应该加在FormAuthenticationFilter中,在认证之前进行验证码校验。 <br />需要写FormAuthenticationFilter的子类,继承FormAuthenticationFilter,改写它的认证方法,在认证之前进行验证码校验。 
    <a name="2oKRD"></a>
    ### 自定义FormAuthenticationFilter
    需要在验证账号和名称之前校验验证码
    
  134. public class CustomFormAuthenticationFilter extends FormAuthenticationFilter {
  135. //原FormAuthenticationFilter的认证方法
  136. @Override
  137. protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
  138. //在这里进行验证码的校验
  139. //从Session中获取正确的验证码
  140. HttpServletRequest httpServletRequest = (HttpServletRequest) request;
  141. HttpSession session = httpServletRequest.getSession();
  142. //取出Session中的验证码(正确的验证码)
  143. String validateCode = (String) session.getAttribute(“validateCode”);
  144. //取出页面的验证码
  145. //输入的验证和session中的验证进行对比
  146. String randomcode = httpServletRequest.getParameter(“randomcode”);
  147. if(randomcode!=null && validateCode!=null && !randomcode.equals(validateCode)){
  148. //如果校验失败,将验证码错误失败信息,通过shiroLoginFailure设置到request中
  149. httpServletRequest.setAttribute(“shiroLoginFailure”, “randomCodeError”);
  150. //拒绝访问,不再校验账号和密码
  151. return true;
  152. }
  153. return super.onAccessDenied(request, response);
  154. }
  155. }
    <a name="hZnkC"></a>
    ### 配置FormAuthenticationFilter
    修改applicationContext-shiro.xml中对FormAuthenticationFilter的配置<br />(1) 在shiroFilter 中添加filters属性
    
  156. ……
    (2)formAuthenticationFilter定义
    
  157. <a name="NQiWt"></a>
    ### 登录页面添加验证码
    
  158. 验证码:
  159. <img
  160. id=”randomcode_img” src=”${baseurl}validatecode.jsp” alt=””
  161. width=”56” height=”20” align=’absMiddle’ /> <a
  162. href=javascript:randomcode_refresh()>刷新
  163. <a name="oWQnt"></a>
    ### 配置validatecode.jsp匿名访问
    ![](https://cdn.nlark.com/yuque/0/2021/png/12851533/1618993113607-75d8c639-e3c9-4d31-bdca-c8fd8a522bac.png#align=left&display=inline&height=358&margin=%5Bobject%20Object%5D&originHeight=358&originWidth=1050&size=0&status=done&style=none&width=1050)
    <a name="ltp3y"></a>
    ### 在login.action对错误信息进行解析
    ![](https://cdn.nlark.com/yuque/0/2021/png/12851533/1618993113675-73d2bc0c-f23a-4146-ba21-c210e7553f23.png#align=left&display=inline&height=512&margin=%5Bobject%20Object%5D&originHeight=512&originWidth=1324&size=0&status=done&style=none&width=1324)<br />
    <a name="eQLet"></a>
    ## 记住我rememberme
    用户登录选择"自动登录" 本次登录成功后会向cookie写身份信息,下次登录从cookie中取出身份信息实现自动登录。
    <a name="kfVYl"></a>
    ### 用户身份实现Serializable序列化接口
    向cookie记录身份信息需要将用户身份信息对象实现序列化接口,如下:<br />![](https://cdn.nlark.com/yuque/0/2021/png/12851533/1618993113606-36ea5d2d-164b-43ba-8151-3b6b1c53696f.png#align=left&display=inline&height=306&margin=%5Bobject%20Object%5D&originHeight=306&originWidth=1000&size=0&status=done&style=none&width=1000)<br />所以还需要将SySPermission类实现序列化接口<br />![](https://cdn.nlark.com/yuque/0/2021/png/12851533/1618993113568-47afd4c1-55c0-4060-9760-559c48614f06.png#align=left&display=inline&height=100&margin=%5Bobject%20Object%5D&originHeight=100&originWidth=928&size=0&status=done&style=none&width=928)
    <a name="VPqkN"></a>
    ### 配置rememberMeManager
    
  164. <a name="4Mro4"></a>
    ### FormAuthenticationFilter配置rememberMe
    修改formAuthenticationFitler添加页面中“记住我checkbox”的input名称
    
  165. <a name="TzqLm"></a>
    ### 登录页面加rememberMe组件
    在login.jsp中添加"记住我"checkbox
    
  166. 自动登陆
  167. ```

    使用UserFilter

    如果设置记住我,下次访问某些URL时可以不用登陆,将记住我即可访问的地址配置让UserFilter拦截。(因为用户身份信息已经存储在名称为rememberMe的cookie中,凡是需要身份认证才能访问的url都可以添加user配置)
    注意:
    user和authc过滤器的区别
    user可以说是针对rememberMe来使用的,当为非匿名访问的某地址配置了user过滤器,那么通过rememberMe不通过登录认证也可直接访问,凡是没有配置user过滤器的URL,即使配置了rememberMe功能也必须通过登录认证才能访问,当然可以匿名访问的除外。/**=user 表示所有的地址都可通过rememberMe功能进行访问。
    authc 表示通过身份信息认证的地址(包括rememberMe或登录实现认证)都可进行访问,范围更广。
    JAVAWEB开发之权限管理(三)——shiro与企业项目整合开发(基于Spring) - 图5

测试:
登录时选中记住我选择框 认证成功后 退出浏览器,重新打开浏览器 直接访问主页
JAVAWEB开发之权限管理(三)——shiro与企业项目整合开发(基于Spring) - 图6
点击修改后效果如下:
JAVAWEB开发之权限管理(三)——shiro与企业项目整合开发(基于Spring) - 图7
可以发现没有配置user过滤器的URL(非匿名的) 是不能通过rememberMe直接访问的
修改配置为所有商品(item)的访问配置user过滤器(实现可通过rememberMe访问)
JAVAWEB开发之权限管理(三)——shiro与企业项目整合开发(基于Spring) - 图8
再重新关闭浏览器 重新打开 可以发现商品的所有链接都可以通过rememberMe进行访问了
JAVAWEB开发之权限管理(三)——shiro与企业项目整合开发(基于Spring) - 图9

项目代码

此项目已上传至GitHub ( https://github.com/LX1993728/permission_web_shiro)
工程目录结构如下:
JAVAWEB开发之权限管理(三)——shiro与企业项目整合开发(基于Spring) - 图10 JAVAWEB开发之权限管理(三)——shiro与企业项目整合开发(基于Spring) - 图11

其关键代码如下:
web.xml

1. <?xml version="1.0" encoding="UTF-8"?>
2. <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3.     xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
4.     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
5.     id="WebApp_ID" version="2.5">
6.     <display-name>springmvc_mybatis_1</display-name>
7. 
8.     <!-- 配置Spring容器监听器 -->
9.     <context-param>
10.         <param-name>contextConfigLocation</param-name>
11.         <param-value>classpath:spring/applicationContext-*.xml</param-value>
12.     </context-param>
13.     <listener>
14.         <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
15.     </listener>
16. 
17.     <!-- 前端控制器 -->
18.     <servlet>
19.         <servlet-name>springmvc</servlet-name>
20.         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
21.         <!-- 加载springmvc配置 -->
22.         <init-param>
23.             <param-name>contextConfigLocation</param-name>
24.             <!-- 配置文件的地址 如果不配置contextConfigLocation,
25.              默认查找的配置文件名称classpath下的:servlet名称+"-serlvet.xml"
26.              即:springmvc-serlvet.xml 
27.              -->
28.             <param-value>classpath:spring/springmvc.xml</param-value>
29.         </init-param>
30.     </servlet>
31.     <servlet-mapping>
32.         <servlet-name>springmvc</servlet-name>
33.         <!-- 可以配置/ ,此工程 所有请求全部由springmvc解析,此种方式可以实现 RESTful方式,需要特殊处理对静态文件的解析不能由springmvc解析 
34.             可以配置*.do或*.action,所有请求的url扩展名为.do或.action由springmvc解析,此种方法常用 不可以/*,如果配置/*,返回jsp也由springmvc解析,这是不对的。 -->
35.         <url-pattern>*.action</url-pattern>
36.     </servlet-mapping>
37. 
38.     <!-- shiro的filter -->
39.     <!-- shiro过滤器,DelegatingFilterProxy通过代理模式将Spring容器中的bean和filter关联起来 -->
40.     <filter>
41.         <filter-name>shiroFilter</filter-name>
42.         <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
43.         <!-- 设置targetFilterLifecycle为true 由servlet控制filter生命周期 -->
44.         <init-param>
45.             <param-name>targetFilterLifecycle</param-name>
46.             <param-value>true</param-value>
47.         </init-param>
48.         <!-- 设置Spring容器filter的bean id,如果不设置则在Spring注册的bean中查找与filter-name一致的bean -->
49.         <init-param>
50.             <param-name>targetBeanName</param-name>
51.             <param-value>shiroFilter</param-value>
52.         </init-param>
53.     </filter>
54.     <filter-mapping>
55.         <filter-name>shiroFilter</filter-name>
56.         <url-pattern>/*</url-pattern>
57.     </filter-mapping>
58. 
59.     <!-- post乱码处理 -->
60.     <filter>
61.         <filter-name>CharacterEncodingFilter</filter-name>
62.         <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
63.         <init-param>
64.             <param-name>encoding</param-name>
65.             <param-value>utf-8</param-value>
66.         </init-param>
67.     </filter>
68.     <filter-mapping>
69.         <filter-name>CharacterEncodingFilter</filter-name>
70.         <url-pattern>/*</url-pattern>
71.     </filter-mapping>
72. 
73. 
74.     <welcome-file-list>
75.         <welcome-file>index.html</welcome-file>
76.         <welcome-file>index.htm</welcome-file>
77.         <welcome-file>index.jsp</welcome-file>
78.         <welcome-file>default.html</welcome-file>
79.         <welcome-file>default.htm</welcome-file>
80.         <welcome-file>default.jsp</welcome-file>
81.     </welcome-file-list>
82. </web-app>

applicationContext-shiro.xml

1. <?xml version="1.0" encoding="UTF-8"?>
2. <beans xmlns="http://www.springframework.org/schema/beans"
3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
4.     xmlns:context="http://www.springframework.org/schema/context"
5.     xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
6.     xsi:schemaLocation="http://www.springframework.org/schema/beans 
7.         http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
8.         http://www.springframework.org/schema/mvc 
9.         http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd 
10.         http://www.springframework.org/schema/context 
11.         http://www.springframework.org/schema/context/spring-context-3.2.xsd 
12.         http://www.springframework.org/schema/aop 
13.         http://www.springframework.org/schema/aop/spring-aop-3.2.xsd 
14.         http://www.springframework.org/schema/tx 
15.         http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
16.     <!-- web.xml中shiro的filter对应的bean -->
17.     <!-- Shiro的web过滤器 -->
18.     <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
19.         <property name="securityManager" ref="securityManager"/>
20.         <!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求此地址将由formAuthenticationFilter进行表单认证 -->
21.         <property name="loginUrl" value="/login.action"/>
22.         <!-- 认证成功统一跳转到first.action,建议不配置,默认情况下,shiro认证成功后自动跳转上一个请求路径 -->
23.         <property name="successUrl" value="/first.action"/>
24.         <!-- 通过unauthorizedUrl 指定没有权限操作时的跳转页面 -->
25.         <property name="unauthorizedUrl" value="/refuse.jsp"/>
26.         <!-- 自定义filter配置 -->
27.         <property name="filters">
28.             <map>
29.                 <!-- 将自定义的FormAuthenticationFilter注入shiroFilter -->
30.                 <entry key="authc" value-ref="authenticationFilter"/>
31.             </map>
32.         </property>
33.         <!-- 过滤器链定义,从上向下执行,一般将/**放在最下边 -->
34.         <property name="filterChainDefinitions">
35.             <value>
36.                 <!-- 退出拦截,请求logout.action执行退出操作 shiro自动清除Session-->
37.                 /logout.action = logout
38.                 <!-- 测试清空缓存 -->
39.                 /clearShiroCache.action = anon
40.                 <!-- 无权访问页面 anon表示可以匿名访问 -->
41.                 /refuse.jsp = anon
42.                 <!-- 验证码可以匿名访问 -->
43.                 /validatecode.jsp = anon
44.                 <!-- perms[xx] 表示有xx权限才可以访问 一般此类使用注解替代配置 -->
45.                 <!-- /item/queryItem.action = perms[item:query] -->
46.                 <!-- /item/editItem.action = perms[item:edit] -->
47.                 <!-- 配置记住我或认证通过可以访问的地址 -->
48.                 /index.jsp = user
49.                 /first.action = user
50.                 /welcome.jsp = user
51.                 <!-- /item/queryItem.action = user -->
52.                 /item/** = user
53.                 <!-- 对静态资源设置匿名访问 -->
54.                 /js/** = anon
55.                 /images/** = anon
56.                 /styles/** = anon
57.                 <!-- /** = authc 表示所有的URL都必须认真通过才可以进行访问-->
58.                 /** = authc    
59.             </value>        
60.         </property>
61.     </bean>
62. 
63.     <!-- 安全管理器 -->
64.     <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
65.         <property name="realm" ref="customRealm" />
66.         <!-- 注入缓存管理器 -->
67.         <property name="cacheManager" ref="cacheManager"/>
68.         <!-- 注入Session管理器 -->
69.         <property name="sessionManager" ref="sessionManager"/>
70.         <!-- 注入rememberMe管理器 -->
71.         <property name="rememberMeManager" ref="rememberMeManager"/>
72.     </bean>
73. 
74.     <!-- 自定义Realm -->
75.     <bean id="customRealm" class="liuxun.ssm.shiro.CustomRealm">
76.         <!-- 将凭证匹配器设置到realm中,realm按照凭证匹配器的要求进行散列 -->
77.         <property name="credentialsMatcher" ref="credentialsMatcher"/>
78.     </bean>
79. 
80.     <!-- 凭证匹配器 -->
81.     <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
82.         <property name="hashAlgorithmName" value="md5"/>
83.         <property name="hashIterations" value="1"/>
84.     </bean>
85. 
86.     <!-- 缓存管理器 -->
87.     <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
88.         <property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
89.     </bean>
90. 
91.     <!-- 会话管理器 -->
92.     <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
93.         <!-- Session的失效时长,单位:毫秒 -->
94.         <property name="globalSessionTimeout" value="600000"/>
95.         <!-- 删除失效的Session -->
96.         <property name="deleteInvalidSessions" value="true"/>
97.     </bean>
98. 
99.     <!-- 自定义form认证过滤器 -->
100.     <!-- 基于Form表单的身份认证过滤器,即使不配置也会注册此过滤器,表单中的用户账号、密码及loginurl将采用默认值,建议配置 -->
101.     <bean id="authenticationFilter" class="liuxun.ssm.shiro.CustomFormAuthenticationFilter">
102.         <!-- 表单中账号的input名称 -->
103.         <property name="usernameParam" value="username"/>
104.         <!-- 表单中密码的input名称 -->
105.         <property name="passwordParam" value="password"/>
106.         <!-- 记住我input的名称 -->
107.         <property name="rememberMeParam" value="rememberMe"/>
108.     </bean>
109. 
110.     <!-- rememberMeManager管理器,写cookie,取出cookie生成用户信息 -->
111.     <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
112.         <property name="cookie" ref="rememberMeCookie"/>
113.     </bean>
114.     <!-- 记住我cookie -->
115.     <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
116.         <!-- rememberMe是cookie的名称 -->
117.         <constructor-arg value="rememberMe"/>
118.         <!-- 记住我cookie的有效时间是30天 30*24*60*60 单位是秒 -->
119.         <property name="maxAge" value="2592000"></property>
120.     </bean>
121. </beans>

springmvc.xml中设计shiro注解的配置如下:

1. <!-- 开启aop,对类代理 -->
2.     <aop:config proxy-target-class="true"></aop:config>
3.     <!-- 开启shiro注解支持 -->
4.     <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
5.         <property name="securityManager" ref="securityManager" />
6.     </bean>

shiro-ehcache.xml

1. <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2.     xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
3.     <!--diskStore:缓存数据持久化的目录 地址  -->
4.     <diskStore path="/Users/liuxun/Desktop/ehcache" />
5.     <defaultCache 
6.         maxElementsInMemory="1000"
7.         maxElementsOnDisk="10000000"
8.         eternal="false"
9.         overflowToDisk="true"
10.         diskPersistent="false"
11.         timeToIdleSeconds="120"
12.         timeToLiveSeconds="120"
13.         diskExpiryThreadIntervalSeconds="120"
14.         memoryStoreEvictionPolicy="LRU">
15.     </defaultCache>
16. </ehcache>

CustomRealm.java

1. package liuxun.ssm.shiro;
2. 
3. import java.util.ArrayList;
4. import java.util.List;
5. 
6. import org.apache.shiro.SecurityUtils;
7. import org.apache.shiro.authc.AuthenticationException;
8. import org.apache.shiro.authc.AuthenticationInfo;
9. import org.apache.shiro.authc.AuthenticationToken;
10. import org.apache.shiro.authc.SimpleAuthenticationInfo;
11. import org.apache.shiro.authc.UsernamePasswordToken;
12. import org.apache.shiro.authz.AuthorizationInfo;
13. import org.apache.shiro.authz.SimpleAuthorizationInfo;
14. import org.apache.shiro.cache.CacheManager;
15. import org.apache.shiro.realm.AuthorizingRealm;
16. import org.apache.shiro.subject.PrincipalCollection;
17. import org.apache.shiro.util.ByteSource;
18. import org.springframework.beans.factory.annotation.Autowired;
19. 
20. import liuxun.ssm.po.ActiveUser;
21. import liuxun.ssm.po.SysPermission;
22. import liuxun.ssm.po.SysUser;
23. import liuxun.ssm.service.SysService;
24. 
25. public class CustomRealm extends AuthorizingRealm {
26.     @Autowired
27.     private SysService sysService;
28. 
29.     // 设置Realm名称
30.     @Override
31.     public void setName(String name) {
32.         super.setName("CustomRealm");
33.     }
34. 
35.     // 支持UsernamePasswordToken
36.     @Override
37.     public boolean supports(AuthenticationToken token) {
38.         return token instanceof UsernamePasswordToken;
39.     }
40. 
41.     // 用于认证(使用静态数据模拟测试)
42.     /**@Override
43.     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
44.         // 从token中获取用户身份信息
45.         String username = (String) token.getPrincipal();
46.         // 拿着username从数据库中进行查询
47.         // ....
48.         // 如果查询不到返回null
49.         if (!username.equals("zhangsan")) {
50.             return null;
51.         }
52. 
53.         // 获取从数据库查询出来的用户密码
54.         String password = "123"; // 这里使用静态数据进行测试
55. 
56.         // 根据用户id从数据库中取出菜单
57.         // ...先使用静态数据
58.         List<SysPermission> menus = new ArrayList<SysPermission>();
59.         SysPermission sysPermission_1 = new SysPermission();
60.         sysPermission_1.setName("商品管理");
61.         sysPermission_1.setUrl("/item/queryItem.action");
62.         SysPermission sysPermission_2 = new SysPermission();
63.         sysPermission_2.setName("用户管理");
64.         sysPermission_2.setUrl("/user/query.action");
65. 
66.         menus.add(sysPermission_1);
67.         menus.add(sysPermission_2);
68. 
69.         // 构建用户身份信息
70.         ActiveUser activeUser = new ActiveUser();
71.         activeUser.setUserid(username);
72.         activeUser.setUsername(username);
73.         activeUser.setUsercode(username);
74.         activeUser.setMenus(menus);
75. 
76.         // 返回认证信息由父类AuthenticationRealm进行认证
77.         SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(activeUser, password,
78.                 this.getName());
79. 
80.         return simpleAuthenticationInfo;
81.     }
82. 
83.     // 用于授权(使用静态数据进行测试)
84.     @Override
85.     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
86.         //获取身份信息
87.         ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();
88.         //用户id
89.         String userid = activeUser.getUserid();
90.         // 根据用户id从数据库中查询权限数据
91.         // ...这里使用静态数据模拟
92.         List<String> permissions = new ArrayList<String>();
93.         permissions.add("item:query");
94.         permissions.add("item:update");
95. 
96.         //将权限信息封装为AuthorizationInfo
97.         SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
98.         //基于资源权限的访问控制
99.         for (String permission : permissions) {
100.             simpleAuthorizationInfo.addStringPermission(permission);
101.         }
102.         // 如果基于角色进行访问控制
103.         // for (String role : roles) {
104.         // simpleAuthorizationInfo.addRole(role);
105.         // }
106. 
107.         return simpleAuthorizationInfo;
108.     }
109.     **/
110.     // 用于认证(从数据库中查询用户信息)
111.     @Override
112.     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
113.         // 从token中获取用户身份信息
114.         String userCode = (String) token.getPrincipal();
115. 
116.         SysUser sysUser = null;
117.         try {
118.             sysUser = sysService.findSysUserByUserCode(userCode);
119.         } catch (Exception e) {
120.             e.printStackTrace();
121.         }
122. 
123.         //如果账号不存在则返回null
124.         if (sysUser == null) {
125.             return null;
126.         }
127. 
128.         //根据用户id取出菜单
129.         List<SysPermission> menus = null;
130.         try {
131.             menus = sysService.findMenuListByUserId(sysUser.getId());
132.         } catch (Exception e) {
133.             e.printStackTrace();
134.         }
135.         //用户密码
136.         String password = sysUser.getPassword();
137.         //盐
138.         String salt = sysUser.getSalt();
139. 
140.         //构建用户身份信息
141.         ActiveUser activeUser = new ActiveUser();
142.         activeUser.setUserid(sysUser.getId());
143.         activeUser.setUsername(sysUser.getUsername());
144.         activeUser.setUsercode(sysUser.getUsercode());
145.         activeUser.setMenus(menus);
146. 
147.         //将activeUser设置simpleAuthenticationInfo
148.         SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(activeUser, password,
149.                 ByteSource.Util.bytes(salt), this.getName());
150.         return simpleAuthenticationInfo;
151.     }
152. 
153.     // 用于授权(从数据库中查询授权信息)
154.     @Override
155.     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
156.         //获取身份信息
157.         ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();
158.         //用户id
159.         String userid = activeUser.getUserid();
160.         //获取用户权限
161.         List<SysPermission> permissionsList = null;
162.         try {
163.             permissionsList = sysService.findPermissionListByUserId(userid);
164.         } catch (Exception e) {
165.             e.printStackTrace();
166.         }
167.         //构建shiro授权信息
168.         SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
169.         //单独定义一个集合
170.         List<String> permissions = new ArrayList<String>();
171.         for (SysPermission sysPermission : permissionsList) {
172.             //将数据库中的权限标签放入集合
173.             permissions.add(sysPermission.getPercode());
174.         }
175.         simpleAuthorizationInfo.addStringPermissions(permissions);
176. 
177.         return simpleAuthorizationInfo;
178.     }
179. 
180.     //清除用户的授权信息
181. 
182. 
183.     //清空缓
184.     public void clearCached(){
185.         //清空所有用户的身份缓存信息
186.         PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
187.         super.clearCache(principals);
188.     }
189. 
190. }

CustomFormAuthenticationFilter.java

1. package liuxun.ssm.shiro;
2. 
3. import javax.servlet.ServletRequest;
4. import javax.servlet.ServletResponse;
5. import javax.servlet.http.HttpServletRequest;
6. import javax.servlet.http.HttpSession;
7. 
8. import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
9. /**
10.  * 认证之前实现验证码校验
11.  * @author liuxun
12.  *
13.  */
14. public class CustomFormAuthenticationFilter extends FormAuthenticationFilter {
15. 
16.     //原FormAuthenticationFilter的认证方法
17.     @Override
18.     protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
19.         //在这里进行验证码的校验
20. 
21.         //从Session中获取正确的验证码
22.         HttpServletRequest httpServletRequest = (HttpServletRequest) request;
23.         HttpSession session = httpServletRequest.getSession();
24.         //取出Session中的验证码(正确的验证码)
25.         String validateCode = (String) session.getAttribute("validateCode");
26. 
27.         //取出页面的验证码
28.         //输入的验证和session中的验证进行对比 
29.         String randomcode = httpServletRequest.getParameter("randomcode");
30.         if(randomcode!=null && validateCode!=null && !randomcode.equals(validateCode)){
31.             //如果校验失败,将验证码错误失败信息,通过shiroLoginFailure设置到request中
32.             httpServletRequest.setAttribute("shiroLoginFailure", "randomCodeError");
33.             //拒绝访问,不再校验账号和密码 
34.             return true; 
35.         }
36.         return super.onAccessDenied(request, response);
37.     }
38. 
39. }

LoginController.java

1. package liuxun.ssm.controller;
2. 
3. import javax.servlet.http.HttpServletRequest;
4. import javax.servlet.http.HttpSession;
5. 
6. import org.apache.shiro.authc.IncorrectCredentialsException;
7. import org.apache.shiro.authc.UnknownAccountException;
8. import org.springframework.beans.factory.annotation.Autowired;
9. import org.springframework.stereotype.Controller;
10. import org.springframework.web.bind.annotation.RequestMapping;
11. 
12. import liuxun.ssm.exception.CustomException;
13. import liuxun.ssm.po.ActiveUser;
14. import liuxun.ssm.service.SysService;
15. 
16. /**
17.  * 登录和退出
18.  * @author liuxun
19.  *
20.  */
21. @Controller
22. public class LoginController {
23. @Autowired
24. private SysService sysService;
25. 
26.     //用户登录提交方法
27.     /*@RequestMapping("/login")
28.     public String login(HttpSession session,String randomcode,String usercode,String password) throws Exception{
29.         // 校验验证码,防止恶性攻击
30.         // 从Session中获取正确的验证码
31.         String validateCode = (String) session.getAttribute("validateCode");
32. 
33.         //输入的验证码和Session中的验证码进行对比
34.         if (!randomcode.equalsIgnoreCase(validateCode)) {
35.             //抛出异常
36.             throw new CustomException("验证码输入错误");
37.         }
38. 
39.         //调用Service校验用户账号和密码的正确性
40.         ActiveUser activeUser = sysService.authenticat(usercode, password);
41. 
42.         //如果Service校验通过,将用户身份记录到Session
43.         session.setAttribute("activeUser", activeUser);
44.         //重定向到商品查询页面
45.         return "redirect:/first.action";
46.     } */
47.     //用户登录提交方法
48.     @RequestMapping("/login")
49.     public String login(HttpServletRequest request) throws Exception{
50. 
51.         //shiro在认证通过后出现错误后将异常类路径通过request返回
52.         //如果登陆失败从request中获取认证异常信息,shiroLoginFailure就是shiro异常类的全限定名
53.         String exceptionClassName = (String)request.getAttribute("shiroLoginFailure");
54.         if (exceptionClassName!=null) {
55.             if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
56.                 throw new CustomException("账号不存在");
57.             } else if(IncorrectCredentialsException.class.getName().equals(exceptionClassName)){
58.                 throw new CustomException("用户名/密码错误");
59.             }else if("randomCodeError".equals(exceptionClassName)){
60.                 throw new CustomException("验证码错误");
61.             } else{
62.                 throw new Exception(); //最终在设置的异常处理器中生成未知错误
63.             }
64.         }
65.         //此方法不处理登录成功(认证成功)的情况
66.         //如果登录失败还到login页面
67.         return "login";
68.     }
69. 
70.     //用户退出
71.     /*
72.     @RequestMapping("/logout")
73.     public String logout(HttpSession session) throws Exception{
74.         //session失效
75.         session.invalidate();
76.         //重定向到商品查询页面
77.         return "redirect:/first.action";
78.     }
79.     */
80. }

FirstAction.java

1. package liuxun.ssm.controller;
2. 
3. import java.util.List;
4. 
5. import javax.servlet.http.HttpSession;
6. 
7. import org.apache.shiro.SecurityUtils;
8. import org.apache.shiro.subject.Subject;
9. import org.springframework.stereotype.Controller;
10. import org.springframework.ui.Model;
11. import org.springframework.web.bind.annotation.RequestMapping;
12. 
13. import liuxun.ssm.po.ActiveUser;
14. 
15. 
16. @Controller
17. public class FirstAction {
18.     //系统首页
19.     @RequestMapping("/first")
20.     public String first(Model model)throws Exception{
21.         //主体
22.         Subject subject = SecurityUtils.getSubject();
23.         //身份
24.         ActiveUser activeUser = (ActiveUser) subject.getPrincipal();
25.         model.addAttribute("activeUser", activeUser);
26.         return "/first";
27.     }
28. 
29.     //欢迎页面
30.     @RequestMapping("/welcome")
31.     public String welcome(Model model)throws Exception{
32. 
33.         return "/welcome";
34. 
35.     }
36. }

其中基于原始的URL拦截(不使用shiro)的代码是屏蔽的代码 方便对比总结 所以没有删除