开发者社区> 技术小阿哥> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

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,
  `namevarchar(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,
  `passwordvarchar(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'nullnullnullnullnull'disable');
INSERT INTO `usr_user` VALUES ('7''contributor''8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92'nullnullnullnullnull'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,如需转载请自行联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
SpringBoot学习笔记-4:第四章 Spring Boot Web 开发(1)
SpringBoot学习笔记-4:第四章 Spring Boot Web 开发
18 0
SpringBoot学习笔记-4:第四章 Spring Boot Web 开发(2)
SpringBoot学习笔记-4:第四章 Spring Boot Web 开发
29 0
使用 ABAP 开发的一个基于 Web Socket 的小工具,能提高程序员日常工作效率
程序员区别于其他岗位的一个优势是,我们可以充分利用自己掌握的编程语言,将平日一些琐碎的,重复的日常工作,通过代码来实现自动化,从而省下更多的时间来投入到技术含量更高的工作中,提高工作效率。
15 0
Python开发自定义Web框架
接收web服务器的动态资源请求,给web服务器提供处理动态资源请求的服务。
10 0
深入解析HTTP(web开发详细理解原理)【JavaWeb】
深入解析HTTP(web开发详细理解原理)【JavaWeb】
22 0
【零基础学Python】后端开发篇第二十节--Python Web开发一:Web开发简介
【零基础学Python】后端开发篇第二十节--Python Web开发一:Web开发简介
25 0
Python Web开发(九):session|token 验证客户端请求
Python Web开发(九):session|token 验证客户端请求
44 0
Python Web开发(十):数据库表的关联
Python Web开发(十):数据库表的关联
19 0
Python Web开发(十一):ORM 对关联表的操作
Python Web开发(十一):ORM 对关联表的操作
16 0
13688
文章
0
问答
文章排行榜
最热
最新