Shiro权限控制框架入门2:如何将Shiro非入侵地整合到SpringMVC等Web项目中

简介:

一 Shiro框架与Spring框架整合的配置

(1)测试项目使用MySQL数据库,其测试SQL语句是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
-- ----------------------------
-- Table structure for usr_func
-- ----------------------------
DROP  TABLE  IF EXISTS `usr_func`;
CREATE  TABLE  `usr_func` (
   `id`  int (11)  NOT  NULL  AUTO_INCREMENT,
   ` name varchar (100)  DEFAULT  NULL ,
   `description`  varchar (100)  DEFAULT  NULL ,
   `code`  varchar (100)  DEFAULT  NULL ,
   `url`  varchar (200)  DEFAULT  NULL ,
   `status`  varchar (20)  DEFAULT  NULL ,
   PRIMARY  KEY  (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11  DEFAULT  CHARSET=utf8;
 
-- ----------------------------
-- Records of usr_func
-- ----------------------------
INSERT  INTO  `usr_func`  VALUES  ( '1' '用户管理-查询' null 'YHGL:CX' null 'enable' );
INSERT  INTO  `usr_func`  VALUES  ( '2' '用户管理-新增' null 'YHGL:XZ' null 'enable' );
INSERT  INTO  `usr_func`  VALUES  ( '3' '用户管理-编辑' null 'YHGL:BJ' null 'enable' );
INSERT  INTO  `usr_func`  VALUES  ( '4' '用户管理-停用' null 'YHGL:TY' null 'enable' );
INSERT  INTO  `usr_func`  VALUES  ( '5' '用户管理-启用' null 'YHGL:QY' null 'enable' );
INSERT  INTO  `usr_func`  VALUES  ( '6' '用户管理-删除' null 'YHGL:SC' null 'enable' );
INSERT  INTO  `usr_func`  VALUES  ( '7' '文章管理-查询' null 'WZGL:CX' null 'enable' );
INSERT  INTO  `usr_func`  VALUES  ( '8' '文章管理-新增' null 'WZGL:XZ' null 'enable' );
INSERT  INTO  `usr_func`  VALUES  ( '9' '文章管理-编辑' null 'WZGL:BJ' null 'enable' );
INSERT  INTO  `usr_func`  VALUES  ( '10' '文章管理-删除' null 'WZGL:SC' null 'enable' );
 
-- ----------------------------
-- Table structure for usr_role
-- ----------------------------
DROP  TABLE  IF EXISTS `usr_role`;
CREATE  TABLE  `usr_role` (
   `id`  int (11)  NOT  NULL  AUTO_INCREMENT,
   `roleName`  varchar (100)  DEFAULT  NULL ,
   `description`  varchar (100)  DEFAULT  NULL ,
   PRIMARY  KEY  (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6  DEFAULT  CHARSET=utf8;
 
-- ----------------------------
-- Records of usr_role
-- ----------------------------
INSERT  INTO  `usr_role`  VALUES  ( '1' 'manager' '管理员' );
INSERT  INTO  `usr_role`  VALUES  ( '2' 'editor' '编辑' );
INSERT  INTO  `usr_role`  VALUES  ( '3' 'author' '作者' );
INSERT  INTO  `usr_role`  VALUES  ( '4' 'subscriber' '订阅者' );
INSERT  INTO  `usr_role`  VALUES  ( '5' 'contributor' '投稿者' );
 
-- ----------------------------
-- Table structure for usr_role_func
-- ----------------------------
DROP  TABLE  IF EXISTS `usr_role_func`;
CREATE  TABLE  `usr_role_func` (
   `id`  int (11)  NOT  NULL  AUTO_INCREMENT,
   `roleId`  int (11)  DEFAULT  NULL ,
   `funcId`  int (11)  DEFAULT  NULL ,
   PRIMARY  KEY  (`id`),
   KEY  `roleId` (`roleId`),
   CONSTRAINT  `roleId`  FOREIGN  KEY  (`roleId`)  REFERENCES  `usr_role` (`id`)  ON  DELETE  CASCADE  ON  UPDATE  CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=20  DEFAULT  CHARSET=utf8;
 
-- ----------------------------
-- Records of usr_role_func
-- ----------------------------
INSERT  INTO  `usr_role_func`  VALUES  ( '1' '1' '1' );
INSERT  INTO  `usr_role_func`  VALUES  ( '2' '1' '2' );
INSERT  INTO  `usr_role_func`  VALUES  ( '3' '1' '3' );
INSERT  INTO  `usr_role_func`  VALUES  ( '4' '1' '4' );
INSERT  INTO  `usr_role_func`  VALUES  ( '5' '1' '5' );
INSERT  INTO  `usr_role_func`  VALUES  ( '6' '1' '6' );
INSERT  INTO  `usr_role_func`  VALUES  ( '7' '1' '7' );
INSERT  INTO  `usr_role_func`  VALUES  ( '8' '1' '8' );
INSERT  INTO  `usr_role_func`  VALUES  ( '9' '1' '9' );
INSERT  INTO  `usr_role_func`  VALUES  ( '10' '1' '10' );
INSERT  INTO  `usr_role_func`  VALUES  ( '11' '2' '7' );
INSERT  INTO  `usr_role_func`  VALUES  ( '12' '2' '8' );
INSERT  INTO  `usr_role_func`  VALUES  ( '13' '2' '9' );
INSERT  INTO  `usr_role_func`  VALUES  ( '14' '2' '10' );
INSERT  INTO  `usr_role_func`  VALUES  ( '15' '3' '7' );
INSERT  INTO  `usr_role_func`  VALUES  ( '16' '3' '8' );
INSERT  INTO  `usr_role_func`  VALUES  ( '17' '3' '9' );
INSERT  INTO  `usr_role_func`  VALUES  ( '18' '4' '7' );
INSERT  INTO  `usr_role_func`  VALUES  ( '19' '5' '8' );
 
-- ----------------------------
-- Table structure for usr_user
-- ----------------------------
DROP  TABLE  IF EXISTS `usr_user`;
CREATE  TABLE  `usr_user` (
   `id`  int (11)  NOT  NULL  AUTO_INCREMENT,
   `username`  varchar (100)  DEFAULT  NULL ,
   ` password varchar (256)  DEFAULT  NULL ,
   `mobile`  varchar (30)  DEFAULT  NULL ,
   `email`  varchar (100)  DEFAULT  NULL ,
   `createTime` datetime  DEFAULT  NULL ,
   `updateTime` datetime  DEFAULT  NULL ,
   `channelId`  int (11)  DEFAULT  NULL ,
   `status`  varchar (20)  DEFAULT  '1' ,
   PRIMARY  KEY  (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8  DEFAULT  CHARSET=utf8;
 
-- ----------------------------
-- Records of usr_user
-- ----------------------------
INSERT  INTO  `usr_user`  VALUES  ( '1' 'admin' '8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918' '110' 'admin@zifangsky.cn' '2016-10-04 10:33:23' '2016-10-06 10:38:40' '1' 'enable' );
INSERT  INTO  `usr_user`  VALUES  ( '2' 'test' '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92' '3456789' 'test@110.com' '2016-10-18 18:25:12' '2016-10-19 18:25:17' '2' 'enable' );
INSERT  INTO  `usr_user`  VALUES  ( '5' 'zifangsky' '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92' '911' 'admin@zifangsky.cn' '2016-10-20 11:46:45' '2016-10-20 11:46:57' '1' 'enable' );
INSERT  INTO  `usr_user`  VALUES  ( '6' 'sub' '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92' null null null null null 'disable' );
INSERT  INTO  `usr_user`  VALUES  ( '7' 'contributor' '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92' null null null null null 'disable' );
 
-- ----------------------------
-- Table structure for usr_user_role
-- ----------------------------
DROP  TABLE  IF EXISTS `usr_user_role`;
CREATE  TABLE  `usr_user_role` (
   `id`  int (11)  NOT  NULL  AUTO_INCREMENT,
   `userId`  int (11)  DEFAULT  NULL ,
   `roleId`  int (11)  DEFAULT  NULL ,
   PRIMARY  KEY  (`id`),
   KEY  `userId` (`userId`),
   CONSTRAINT  `userId`  FOREIGN  KEY  (`userId`)  REFERENCES  `usr_user` (`id`)  ON  DELETE  CASCADE  ON  UPDATE  CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=4  DEFAULT  CHARSET=utf8;
 
-- ----------------------------
-- Records of usr_user_role
-- ----------------------------
INSERT  INTO  `usr_user_role`  VALUES  ( '1' '1' '1' );
INSERT  INTO  `usr_user_role`  VALUES  ( '2' '5' '3' );
INSERT  INTO  `usr_user_role`  VALUES  ( '3' '5' '5' );
INSERT  INTO  `usr_user_role`  VALUES  ( '4' '2' '4' );
INSERT  INTO  `usr_user_role`  VALUES  ( '5' '6' '2' );

上面的5张表合起来正好是一个简单的RBAC模型,当然上篇文章的末尾地方也提到过方面的内容。需要注意的是,密码没有使用明文密码,而是经过了简单的SHA256加密,其密码原文分别是admin和123456

(2)在web.xml文件中添加shiro的拦截范围:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- shiro -->
< filter >
     < filter-name >shiroFilter</ filter-name >
     < filter-class >org.springframework.web.filter.DelegatingFilterProxy</ filter-class >
     < init-param >
         < param-name >targetFilterLifecycle</ param-name >
         < param-value >true</ param-value >
     </ init-param >
</ filter >
< filter-mapping >
     < filter-name >shiroFilter</ filter-name >
     < url-pattern >*.html</ url-pattern >
     < dispatcher >REQUEST</ dispatcher >
     < dispatcher >FORWARD</ dispatcher >
</ filter-mapping >
< filter-mapping >
     < filter-name >shiroFilter</ filter-name >
     < url-pattern >*.json</ url-pattern >
     < dispatcher >REQUEST</ dispatcher >
     < dispatcher >FORWARD</ dispatcher >
</ filter-mapping >

因为我设置的spring mvc的拦截范围是.html 和 .json格式的url,同时项目中凡是涉及到业务逻辑的地方都经过了SpringMVC而不是直接使用jsp来访问,因此我这里设置shiro管理权限的部分也就是这两个url格式

(3)缓存采用Ehcache:

在Spring的配置文件context.xml中是这样配置缓存的:

1
2
3
4
5
6
7
8
9
10
11
<!-- Spring提供的基于的Ehcache实现的缓存管理器 -->
< bean  id = "cacheManagerFactory"
     class = "org.springframework.cache.ehcache.EhCacheManagerFactoryBean" >
     < property  name = "configLocation"  value = "classpath:ehcache.xml"  />
</ bean >
 
< bean  id = "ehcacheManager"  class = "org.springframework.cache.ehcache.EhCacheCacheManager" >
     < property  name = "cacheManager"  ref = "cacheManagerFactory"  />
</ bean >
<!-- 启用缓存注解功能  -->
< cache:annotation-driven  cache-manager = "ehcacheManager"  />

(4)shiro相关配置文件context_shiro.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<? 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:jee = "http://www.springframework.org/schema/jee"
     xsi:schemaLocation="http://www.springframework.org/schema/beans 
             http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
             http://www.springframework.org/schema/jee 
             http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
             http://www.springframework.org/schema/aop 
             http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
             http://www.springframework.org/schema/context 
             http://www.springframework.org/schema/context/spring-context-4.0.xsd
             http://www.springframework.org/schema/tx 
             http://www.springframework.org/schema/tx/spring-tx-4.0.xsd"
     xmlns:context = "http://www.springframework.org/schema/context"  xmlns:tx = "http://www.springframework.org/schema/tx"
     xmlns:aop = "http://www.springframework.org/schema/aop" >
     < description >Shiro 配置</ description >
 
     <!-- 缓存管理 -->
     < bean  id = "cacheManager"  class = "org.apache.shiro.cache.ehcache.EhCacheManager" >
         < property  name = "cacheManager"  ref = "cacheManagerFactory"  />
         < property  name = "cacheManagerConfigFile"  value = "classpath:ehcache.xml"  />
     </ bean >
 
     <!-- Shiro安全管理器 -->
     < bean  id = "securityManager"  class = "org.apache.shiro.web.mgt.DefaultWebSecurityManager" >
         < property  name = "realm"  ref = "customRealm" ></ property >
         < property  name = "cacheManager"  ref = "cacheManager" ></ property >
     </ bean >
     
     <!-- 自定义Realm -->
     < bean  id = "customRealm"  class = "cn.zifangsky.security.CustomRealm"  />
     
     < bean  id = "shiroFilter"  class = "org.apache.shiro.spring.web.ShiroFilterFactoryBean" >
         < property  name = "securityManager"  ref = "securityManager"  />
         < property  name = "loginUrl"  value = "/user/user/login.html"  />
         <!-- <property name="successUrl" value="/user/index.html" /> -->
         < property  name = "unauthorizedUrl"  value = "/error/403.jsp"  />
         < property  name = "filters" >
             < map >
                 < entry  key = "auth"  value-ref = "userFilter" />
                 < entry  key = "login"  value-ref = "loginFilter" />
                 < entry  key = "clean"  value-ref = "cleanFilter" />
             </ map >  
         </ property >
         < property  name = "filterChainDefinitions" >
             < value >
                 /error/*= anon
                 /user/index.html = login
                 /user/user/logout.html = logout
                 /user/user/admin.html = roles[manager]
                 /article/index* = perms[WZGL:CX]
                 /article/add* = perms[WZGL:XZ]
                 /article/edit* = perms[WZGL:BJ]
                 /**/*.htm* = auth
                 /**/*.json* = auth
             </ value >
         </ property >
     </ bean >
 
     < bean  id = "userFilter"  class = "cn.zifangsky.security.CustomUserFilter" >
         < property  name = "ignoreList" >
             < list >
                 < value >/user/user/login.html</ value >
                 < value >/user/user/check.json</ value >
                 < value >/user/user/verify.html</ value >
                 < value >/user/user/checkVerifyCode.json</ value >
             </ list >
         </ property >
     </ bean >
     
     < bean  id = "loginFilter"  class = "cn.zifangsky.security.LoginFilter" />
     < bean  id = "cleanFilter"  class = "cn.zifangsky.security.CleanFilter" />
     < bean  id = "lifecycleBeanPostProcessor"  class = "org.apache.shiro.spring.LifecycleBeanPostProcessor" />
 
</ beans >

关于这里的shiro的一些配置,我觉得以下几点需要简单说明下:

i)缓存管理:

可以看出,我这里是引用了前面定义好的Ehcache工厂——cacheManagerFactory

ii)自定义Realm:

在配置securityManager这里,为了能够在后面更灵活地控制权限认证和授权过程,因此这里定义了一个自定义的Realm。其具体代码是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package  cn.zifangsky.security;
 
import  java.util.ArrayList;
import  java.util.HashSet;
import  java.util.List;
import  java.util.Set;
 
import  javax.servlet.http.HttpServletRequest;
 
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.authz.AuthorizationInfo;
import  org.apache.shiro.authz.SimpleAuthorizationInfo;
import  org.apache.shiro.realm.AuthorizingRealm;
import  org.apache.shiro.subject.PrincipalCollection;
import  org.springframework.web.context.request.RequestContextHolder;
import  org.springframework.web.context.request.ServletRequestAttributes;
 
import  cn.zifangsky.model.UsrFunc;
import  cn.zifangsky.model.bo.UsrRoleBO;
import  cn.zifangsky.model.bo.UsrUserBO;
 
/**
  * 自定义Realm,用于自定义登录认证以及自定义授权管理
  * @author zifangsky
  *
  */
public  class  CustomRealm  extends  AuthorizingRealm {
 
     /**
      * 返回一个唯一的Realm名称
      * */
     @Override
     public  String getName() {
         return  "CustomRealm" ;
     }
     
     /**
      * 授权
      */
     @Override
     protected  AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
         SimpleAuthorizationInfo info =  null ;
         UsrUserBO userBO = (UsrUserBO) principalCollection.fromRealm(getName()).iterator().next();
         if (userBO !=  null ){
             info =  new  SimpleAuthorizationInfo();
             List<UsrRoleBO> roleBOs = userBO.getUsrRoleBOs();   //所有角色
             if (roleBOs !=  null  && roleBOs.size() >  0 ){
                 List<String> roleNames =  new  ArrayList<>();   //所有角色名
                 Set<String> funcCodes =  new  HashSet<>();   //该用户拥有的所有角色的所有权限的code集合
                 
                 for (UsrRoleBO roleBO : roleBOs){
                     roleNames.add(roleBO.getRolename());
                     
                     List<UsrFunc> funcs = roleBO.getFuncs();   //一个角色的所有权限的code集合
                     if (funcs !=  null  && funcs.size() >  0 ){
                         for (UsrFunc f : funcs){
                             funcCodes.add(f.getCode());
                         }
                     }
                 }
                 //添加所有的角色和权限
                 info.addRoles(roleNames);
                 info.addStringPermissions(funcCodes);
             }
         }
         return  info;
     }
 
     /**
      * 认证
      * 在这里由于登录之后在session中已经存在用户信息了,同时这里的token里的认证信息也是在
      * cn.zifangsky.security.LoginFilter的createToken方法中从session获取的用户信息然后再添加的。
      * 因此这里就省略了对token里的信息的校验步骤,直接从session中获取userBO并返回认证之后的凭证
      */
     @Override
     protected  AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)  throws  AuthenticationException {
         ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
         HttpServletRequest request = attributes.getRequest();
         
         UsrUserBO userBO = (UsrUserBO) request.getSession().getAttribute( "userBO" );
         SimpleAuthenticationInfo info =  null ;
         if (userBO !=  null ){
             info =  new  SimpleAuthenticationInfo(userBO, userBO.getPassword(), getName());
         }
         
         return  info;
     }
 
}

这里的代码逻辑并不复杂,如果动手试验过我在上篇文章中介绍的基础知识的话,那么这里理解起来将会更加容易了。在授权部分能够获取一个用户的所有角色和权限信息,那是因为在常规的登录成功之后就将这些信息放到session中了

iii)自定义过滤器:

从上面的配置文件可以看出,我这里定义了3个过滤器,分别是:auth、login、clean。auth这个过滤器就是在shiro原来的认证过滤器的基础上添加了不需要shiro权限认证的url列表;而login则是在常规的登录成功之后跳转到主页时通过从session中取出用户实体生成shiro认证时需要的信息。至于为什么这样做而不是在常规登录过程中将shiro的认证注入进去,我的答案是:为了将shiro的权限管理与我们的业务逻辑尽可能地解耦合,这样以后换另一种权限管理框架时也可以不用修改业务代码

LoginFilter这个类的具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package  cn.zifangsky.security;
 
import  javax.servlet.ServletRequest;
import  javax.servlet.ServletResponse;
import  javax.servlet.http.HttpServletRequest;
 
import  org.apache.shiro.authc.AuthenticationToken;
import  org.apache.shiro.authc.UsernamePasswordToken;
import  org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import  org.springframework.web.context.request.RequestContextHolder;
import  org.springframework.web.context.request.ServletRequestAttributes;
 
import  cn.zifangsky.model.bo.UsrUserBO;
 
public  class  LoginFilter  extends  AuthenticatingFilter {
 
     /**
      * 生成需要认证的信息
      */
     @Override
     protected  AuthenticationToken createToken(ServletRequest request, ServletResponse response)  throws  Exception {
         UsernamePasswordToken token =  null ;
         
         ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
         HttpServletRequest r = attributes.getRequest();
         
         //从session中取出用户
         UsrUserBO userBO = (UsrUserBO) r.getSession().getAttribute( "userBO" );
         if (userBO !=  null ){
             token =  new  UsernamePasswordToken(userBO.getUsername(), userBO.getPassword(),  false , getHost(request));
         }
         return  token;
     }
 
     @Override
     protected  boolean  onAccessDenied(ServletRequest request, ServletResponse response)  throws  Exception {
         return  this .executeLogin(request, response);
     }
 
}

关于这个地方的代码执行流程是这样的:

a)登录成功访问主页时,因为此时在shiro中并没有任何凭证信息,因此认证失败,转向执行上面的onAccessDenied方法。当然,我这里设置的是继续拦截,执行从父类AuthenticatingFilter中继承来的executeLogin方法

b)关于AuthenticatingFilter这个类中的executeLogin方法,其源码是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
     protected  boolean  executeLogin(ServletRequest request, ServletResponse response)  throws  Exception {
         AuthenticationToken token = createToken(request, response);
         if  (token ==  null ) {
             String msg =  "createToken method implementation returned null. A valid non-null AuthenticationToken "  +
                     "must be created in order to execute a login attempt." ;
             throw  new  IllegalStateException(msg);
         }
         try  {
             Subject subject = getSubject(request, response);
             subject.login(token);
             return  onLoginSuccess(token, subject, request, response);
         catch  (AuthenticationException e) {
             return  onLoginFailure(token, e, request, response);
         }
     }

可以看出,这里先是执行createToken方法获取了认证信息,最后再是返回onLoginSuccess方法的执行结果(PS:这个方法默认是true)

而createToken这个方法我这里是复写了的,其目的是从session中获取待认证的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
  * 生成需要认证的信息
  */
@Override
protected  AuthenticationToken createToken(ServletRequest request, ServletResponse response)  throws  Exception {
     UsernamePasswordToken token =  null ;
     
     ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
     HttpServletRequest r = attributes.getRequest();
     
     //从session中取出用户
     UsrUserBO userBO = (UsrUserBO) r.getSession().getAttribute( "userBO" );
     if (userBO !=  null ){
         token =  new  UsernamePasswordToken(userBO.getUsername(), userBO.getPassword(),  false , getHost(request));
     }
     return  token;
}

当然,最后还会执行我自定义的CustomRealm类中的doGetAuthenticationInfo用于校验认证信息,实现shiro的“登录”。在这里,因为都是从session中获取登录成功的用户的详细信息,因此就省略了校验这一步骤,直接从session中取出数据并返回认证成功之后的凭证信息,到此,整个认证流程就结束了

我觉得以上的流程还是比较合理的,因为上面这一执行流程并没有和常规的登录业务进行耦合。相反,我发现一些网友直接将上面executeLogin方法中执行的一些代码插入到常规的的登录流程中,我个人认为那样做是不太优雅的。当然,那样做理解起来稍微简单一些,看大家个人的看法吧

iv)shiro默认拦截器:

在上面的shiro的配置文件中,我除了使用了几个自定义的过滤器之外,还使用了几个shiro默认拦截器。shiro常用的几个默认拦截器分别是:

  • authc    基于表单的拦截器,如“/**=authc”,如果没有登录会跳到相应的登录页面登录。当然,我这里使用时经过了包装,也就是“auth”

  • anon    匿名拦截器,即不需要登录即可访问,一般用于过滤静态资源

  • logout    退出拦截器

  • roles    用于验证用户拥有指定角色才可以访问

  • perms    用于验证用户拥有指定权限才可以访问

到此,shiro相关的配置就已经结束了

二 常规的登录流程

(1)controller层的登录、注销方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
  * 登录校验
 
  * @param UsrUser
  *            登录时的User对象,包括form表单中的用户名和密码
  * @param request
  * @return 是否登录成功标志
  */
@RequestMapping (value =  "/user/user/check.json" ,method={RequestMethod.POST})
@ResponseBody
public  Map<String, String> loginCheck( @RequestBody  UsrUser user,HttpServletRequest request) {
     HttpSession session = request.getSession();
     Map<String, String> result =  new  HashMap<>();
 
     Boolean codeCheck = (Boolean) session.getAttribute( "codeCheck" );   //验证码是否校验成功标志
     session.removeAttribute( "codeCheck" );
     
     if  (codeCheck !=  null  && codeCheck) {
         UsrUserBO userBO = userManager.login(user.getUsername(), user.getPassword());
         if  (userBO !=  null ) {
             session.setAttribute( "userBO" , userBO);  // 登录成功之后加入session中
             result.put( "result" "success" );
         else  {
             result.put( "result" "error" );
         }
     } else {
         result.put( "result" "error" );
     }
     return  result;
}
 
/**
  * 注销登录
 
  * @param request
  * @return 重定向回登录页面
  */
@RequestMapping ( "/user/user/logout.html" )
public  ModelAndView logout(HttpServletRequest request) {
     userManager.logout(request.getSession());
     return  new  ModelAndView( "redirect:/user/user/login.html" );
}

注:我这里的登录采用的是异步登录,并且并没有任何与Shiro相关的内容

(2)login方法对应的业务逻辑层的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@Override
public  UsrUserBO login(String username, String password) {
     if  (StringUtils.isNotBlank(username) && StringUtils.isNotBlank(password)) {
         UsrUserBO result =  new  UsrUserBO();
         // 密码采用sha256加密
         UsrUser user = userMapper.selectByNamePasswd(username, EncryptionUtil.sha256Hex(password));
         logger.info( "登录,用户: "  + username);
         
         if (user !=  null ){
             //根据用户id查询所有角色
             List<UsrRole> roles = userRoleMapper.selectRolesByUserId(user.getId());
             
             List<UsrRoleBO> roleBOs =  new  ArrayList<>();
             if (roles !=  null  && roles.size() >  0 ){
                 for (UsrRole role : roles){
                     //根据角色id查询所有权限
                     List<UsrFunc> funcs = roleFuncMapper.selectFuncsByRoleId(role.getId());
                     
                     UsrRoleBO roleBO =  new  UsrRoleBO();
                     roleBO.setId(role.getId());
                     roleBO.setRolename(role.getRolename());
                     roleBO.setDescription(role.getDescription());
                     roleBO.setFuncs(funcs);   //角色对应的所有权限
                     
                     roleBOs.add(roleBO);
                 }
             }
             result.setId(user.getId());
             result.setUsername(user.getUsername());
             result.setPassword(user.getPassword());
             result.setMobile(user.getMobile());
             result.setEmail(user.getEmail());
             result.setChannelid(user.getChannelid());
             result.setCreatetime(user.getCreatetime());
             result.setUpdatetime(user.getUpdatetime());
             result.setStatus(user.getStatus());
             result.setUsrRoleBOs(roleBOs);   //用户对应的所有角色
             
             return  result;
         }
         return  null ;
     }
     return  null ;
}

注:i)UsrUserBO实体类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package  cn.zifangsky.model.bo;
 
import  java.util.List;
 
import  cn.zifangsky.model.UsrUser;
 
public  class  UsrUserBO  extends  UsrUser {
     private  List<UsrRoleBO> usrRoleBOs;
 
     public  List<UsrRoleBO> getUsrRoleBOs() {
         return  usrRoleBOs;
     }
 
     public  void  setUsrRoleBOs(List<UsrRoleBO> usrRoleBOs) {
         this .usrRoleBOs = usrRoleBOs;
     }
}

ii)UsrRoleBO实体类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package  cn.zifangsky.model.bo;
 
import  java.util.List;
 
import  cn.zifangsky.model.UsrFunc;
import  cn.zifangsky.model.UsrRole;
 
public  class  UsrRoleBO  extends  UsrRole {
     private  List<UsrFunc> funcs;
 
     public  List<UsrFunc> getFuncs() {
         return  funcs;
     }
 
     public  void  setFuncs(List<UsrFunc> funcs) {
         this .funcs = funcs;
     }
}

三 测试

(1)运行这个测试项目后使用test/123456登录,并访问:/user/user/admin.html

wKioL1hu_1GgPBkcAAAqFQA-QJQ754.png

可以发现,最后会跳转到/error/403.jsp页面,因为我们在shiro的配置文件中定义了访问“/user/user/admin.html”需要有“manager”角色,显然 test 用户并没有这个角色

(2)注销之后使用 admin/admin登录,并再次访问:/user/user/admin.html

wKiom1hu_2rBXZnuAAAv9uWOddc251.png

可以发现,现在就有足够的权限访问这个页面了

特别注意:在本篇文章中我粘贴的代码并不完整(PS:其他配置文件、视图页面等)。因此,如果想要运行这个测试项目的话还请在github下载完整源代码。同时,我在shiro的配置文件中还定义了几个其他的权限验证的配置(如:/article/index* = perms[WZGL:CX]),感兴趣的同学也可以在下载源代码之后测试下

四 基于注解的Shiro权限控制

在上面的代码中,我都是将权限控制相关的代码配置在了shiro的配置文件中的“filterChainDefinitions”这一项。但是,这种方式的权限控制明显是有点呆板的,因为如果在将权限设计得非常分散的话,如果都像上面这样配置,那么shiro的配置文件将变得非常臃肿。这时,我们就可以考虑通过在controller层的处理方法上添加shiro的注解来控制权限了。具体的配置如下:

(1)在SpringMVC的配置文件中添加以下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
     <!-- 启用shiro的注解功能 -->
     < bean  class = "org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"  depends-on = "lifecycleBeanPostProcessor" >
         < property  name = "proxyTargetClass"  value = "true"  />
     </ bean >
 
     < bean  class = "org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor" >
         < property  name = "securityManager"  ref = "securityManager" />
     </ bean >
     < bean  class = "org.springframework.web.servlet.handler.SimpleMappingExceptionResolver" >
         < property  name = "exceptionMappings" >
             < props >
                 <!-- 登录 -->
                 < prop  key = "org.apache.shiro.authz.UnauthenticatedException" >
                     redirect:redirect:/user/user/login.html
                 </ prop >
                 <!-- 授权 -->
                 < prop  key = "org.apache.shiro.authz.UnauthorizedException" >
                     redirect:/error/403.jsp
                 </ prop >
             </ props >
         </ property >
         < property  name = "defaultErrorView"  value = "/error/error.jsp" />
     </ bean >

(2)在controller层的一个方法上面添加@RequiresPermissions注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package  cn.zifangsky.controller;
 
import  org.apache.shiro.authz.annotation.RequiresPermissions;
import  org.springframework.stereotype.Controller;
import  org.springframework.web.bind.annotation.RequestMapping;
import  org.springframework.web.servlet.ModelAndView;
 
@Controller
public  class  AirticleController {
     
     @RequestMapping ( "/article/delete.html" )
     @RequiresPermissions ({  "WZGL:SC"  })
     public  ModelAndView test() {
         System.out.println( "拥有'文章管理-删除'的权限" );
         return  new  ModelAndView( "/article/delete" );
     }
}

这个注解很简单,顾名思义就是需要拥有某个权限才能访问这个方法。很显然,代表权限的这些自定义code在登录完成访问主页的时候通过CustomRealm类中的doGetAuthorizationInfo方法就被托管给Shiro了,因此这里就可以直接校验用户是否拥有某个权限了

(3)测试:

使用sub/123456 登录,并访问:/article/delete.html

wKioL1hu_6iDnayIAAAv9uWOddc702.png

很明显,能够正常访问

(2)接着注销后使用 zifangsky /123456 登录,同样访问:/article/delete.html

wKioL1hu_8Lh0a2GAAAyHrsdg9k014.png

可以发现,并没有权限访问,页面自动跳转到403界面去了

到此,关于Shiro入门的一些常见用法的介绍就到此结束了。感谢大家的浏览,如有疑问请在下面评论中留言,我将会认真回复,谢谢!



本文转自 pangfc 51CTO博客,原文链接:http://blog.51cto.com/983836259/1889600,如需转载请自行联系原作者

相关文章
|
2月前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
59 4
|
2月前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
165 3
|
6天前
|
设计模式 前端开发 Java
步步深入SpringMvc DispatcherServlet源码掌握springmvc全流程原理
通过对 `DispatcherServlet`源码的深入剖析,我们了解了SpringMVC请求处理的全流程。`DispatcherServlet`作为前端控制器,负责请求的接收和分发,处理器映射和适配负责将请求分派到具体的处理器方法,视图解析器负责生成和渲染视图。理解这些核心组件及其交互原理,有助于开发者更好地使用和扩展SpringMVC框架。
23 4
|
1月前
|
设计模式 前端开发 数据库
Python Web开发:Django框架下的全栈开发实战
【10月更文挑战第27天】本文介绍了Django框架在Python Web开发中的应用,涵盖了Django与Flask等框架的比较、项目结构、模型、视图、模板和URL配置等内容,并展示了实际代码示例,帮助读者快速掌握Django全栈开发的核心技术。
175 45
|
24天前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
72 2
|
29天前
|
开发框架 搜索推荐 数据可视化
Django框架适合开发哪种类型的Web应用程序?
Django 框架凭借其强大的功能、稳定性和可扩展性,几乎可以适应各种类型的 Web 应用程序开发需求。无论是简单的网站还是复杂的企业级系统,Django 都能提供可靠的支持,帮助开发者快速构建高质量的应用。同时,其活跃的社区和丰富的资源也为开发者在项目实施过程中提供了有力的保障。
|
28天前
|
开发框架 JavaScript 前端开发
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势。通过明确的类型定义,TypeScript 能够在编码阶段发现潜在错误,提高代码质量;支持组件的清晰定义与复用,增强代码的可维护性;与 React、Vue 等框架结合,提供更佳的开发体验;适用于大型项目,优化代码结构和性能。随着 Web 技术的发展,TypeScript 的应用前景广阔,将继续引领 Web 开发的新趋势。
36 2
|
1月前
|
中间件 Go API
Go语言中几种流行的Web框架,如Beego、Gin和Echo,分析了它们的特点、性能及适用场景,并讨论了如何根据项目需求、性能要求、团队经验和社区支持等因素选择最合适的框架
本文概述了Go语言中几种流行的Web框架,如Beego、Gin和Echo,分析了它们的特点、性能及适用场景,并讨论了如何根据项目需求、性能要求、团队经验和社区支持等因素选择最合适的框架。
75 1
|
2月前
|
设计模式 前端开发 Java
Spring MVC——项目创建和建立请求连接
MVC是一种软件架构设计模式,将应用分为模型、视图和控制器三部分。Spring MVC是基于MVC模式的Web框架,通过`@RequestMapping`等注解实现URL路由映射,支持GET和POST请求,并可传递参数。创建Spring MVC项目与Spring Boot类似,使用`@RestController`注解标记控制器类。
45 1
Spring MVC——项目创建和建立请求连接
|
1月前
|
SQL 安全 PHP
探索PHP的现代演进:从Web开发到框架创新
PHP是一种流行的服务器端脚本语言,自诞生以来在Web开发领域占据重要地位。从简单的网页脚本到支持面向对象编程的现代语言,PHP经历了多次重大更新。本文探讨PHP的现代演进历程,重点介绍其在Web开发中的应用及框架创新,如Laravel、Symfony等。这些框架不仅简化了开发流程,还提高了开发效率和安全性。
32 3