一 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
可以发现,最后会跳转到/error/403.jsp页面,因为我们在shiro的配置文件中定义了访问“/user/user/admin.html”需要有“manager”角色,显然 test 用户并没有这个角色
(2)注销之后使用 admin/admin登录,并再次访问:/user/user/admin.html
可以发现,现在就有足够的权限访问这个页面了
特别注意:在本篇文章中我粘贴的代码并不完整(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
很明显,能够正常访问
(2)接着注销后使用 zifangsky /123456 登录,同样访问:/article/delete.html
可以发现,并没有权限访问,页面自动跳转到403界面去了
到此,关于Shiro入门的一些常见用法的介绍就到此结束了。感谢大家的浏览,如有疑问请在下面评论中留言,我将会认真回复,谢谢!
本文转自 pangfc 51CTO博客,原文链接:http://blog.51cto.com/983836259/1889600,如需转载请自行联系原作者