Shiro学习

Apache Shiro是一个开源安全框架,提供身份验证、授权、密码学和会话管理。Shiro框架直观、易用,同时能提供健壮的安全性。

Shiro

1.Apache Shiro是一个强大且易用的Java安全框架,能够用于身份验证、授权、加密和会话管理。Shiro拥有易于理解的API,您可以快速、轻松地获得任何应用程序——从最小的移动应用程序到最大的网络和企业应用程序。

2.Shiro三个核心组件:

  • Subject

    当前操作用户,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物,仅仅意味着当前跟软件交互的东西,Subject代表了当前用户的安全操作。

  • SecurityManager

    管理所有用户的安全操作,是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。

  • Realms

    充当Shiro与应用安全数据间的”桥梁”或者”连接器”。当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果系统默认的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。

权限管理

1.基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。

2.权限管理(认证授权):对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问

  • 身份认证(判断一个用户是否为合法用户的处理过程。如系统通过核对用户输入的用户名和口令与系统中存储的该用户的用户名和口令是否一致来判断用户身份是否正确)
  • 授权(访问控制,主体进行身份认证后需要分配权限方可访问系统资源)

简介

  1. Authentication(认证)、Authorization(授权)、Session Management(会话管理)、Cryptography(加密)被 Shiro 框架的开发团队称之为应用安全的四大基石。
  • Authentication(认证):用户身份识别(用户登录)
  • Authorization(授权):访问控制
  • Session Management(会话管理):特定于用户的会话管理
  • Cryptography(加密):对数据源使用加密算法加密

2.核心架构

  • Subject:

    • 主体,外部应用与subject进行交互,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序

    • Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权

  • SecurityManager

    • 安全管理器,对全部的subject进行安全管理
    • shiro的核心,负责对所有的subject进行安全管理
    • 通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等
    • SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口
  • Authenticator

    • 认证器,对用户身份进行认证
    • Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可自定义认证器
  • Authorizer

    • 授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限
  • Realm

    • 领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,如用户身份数据在数据库那么realm就需要从数据库获取用户身份信息
    • realmz不止从数据源取数据,还包括认证授权校验等。
  • SessionManager

    • 会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。
  • SessionDAO

    • 会话dao,是对session会话操作的一套接口,如将session存储到数据库。
  • CacheManager

    • 缓存管理,将用户权限数据存储在缓存,提高性能。
  • Cryptography

    • 密码管理,shiro提供了一套加密/解密的组件,方便开发(MD5)。

认证

1.shiro认证中的关键对象:

  • Subject(主体):访问系统的用户,主体可以是进行认证的用户、程序等
  • Principal(身份信息):主体进行身份认证的标识,标识具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但必须有一个主身份(Primary Principal)
  • credential(凭证信息):只有主体知道的安全信息,如密码、证书等。

2.shiro 配置文件(.ini)

  • INI文件是一种无固定标准格式的配置文件。它以简单的文字与简单的结构组成,常常使用在Windows操作系统上(后用注册表代替),许多程序也会采用INI文件做为配置文件之用。
  • 节 [section]
  • 参数 name=value
  • 注解 分号(;)
1
2
3
4
5
6
7
8
9
>;last modified 1 April 2001 by John Doe
>[owner]
>name=John Doe
>organization=Acme Products

>[database]
>server=192.0.2.42
>port=143
>file="acme payroll.dat"
  • shiro.ini文件默认在/WEB-INF/或classpath下,INI配置文件一般适用于用户少且不需要在运行时动态创建的情景下使用(学习中模拟系统权限数据)。

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.8.0</version>
</dependency>
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
public class TestAuthenticator {
public static void main(String[] args) {
/*1.创建安全管理器*/
DefaultSecurityManager securityManager = new DefaultSecurityManager();
/*2.安全管理器设置realm*/
securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
/*3.
SecurityUtils 安全工具类
设置安全管理器
*/
SecurityUtils.setSecurityManager(securityManager);
/*4.关键对象subject主体*/
Subject subject = SecurityUtils.getSubject();
/*5.创建令牌*/
UsernamePasswordToken token = new UsernamePasswordToken("root", "1234");
/*6.用户认证*/
try {
System.out.println("认证状态: "+subject.isAuthenticated());
subject.login(token);
System.out.println("认证状态: "+subject.isAuthenticated());

}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("认证失败:当前用户不存在");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("认证失败:密码错误");
}
}
}

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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
/*自定义类*/
subject.login(token);

/*DelegatingSubject*/
public void login(AuthenticationToken token) throws AuthenticationException {
this.clearRunAsIdentitiesInternal();
Subject subject = this.securityManager.login(this, token);
String host = null;
PrincipalCollection principals;
if (subject instanceof DelegatingSubject) {
DelegatingSubject delegating = (DelegatingSubject)subject;
principals = delegating.principals;
host = delegating.host;
} else {
principals = subject.getPrincipals();
}

if (principals != null && !principals.isEmpty()) {
this.principals = principals;
this.authenticated = true;
if (token instanceof HostAuthenticationToken) {
host = ((HostAuthenticationToken)token).getHost();
}

if (host != null) {
this.host = host;
}

Session session = subject.getSession(false);
if (session != null) {
this.session = this.decorate(session);
} else {
this.session = null;
}

} else {
String msg = "Principals returned from securityManager.login( token ) returned a null or empty value. This value must be non null and populated with one or more elements.";
throw new IllegalStateException(msg);
}
}

/*DefaultSecurityManager*/
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
info = this.authenticate(token);
} catch (AuthenticationException var7) {
AuthenticationException ae = var7;

try {
this.onFailedLogin(token, ae, subject);
} catch (Exception var6) {
if (log.isInfoEnabled()) {
log.info("onFailedLogin method threw an exception. Logging and propagating original AuthenticationException.", var6);
}
}

throw var7;
}

Subject loggedIn = this.createSubject(token, info, subject);
this.onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
}

/*AuthenticatingSecurityManager*/
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
return this.authenticator.authenticate(token);
}

/*AbstractAuthenticator */
public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
if (token == null) {
throw new IllegalArgumentException("Method argument (authentication token) cannot be null.");
} else {
log.trace("Authentication attempt received for token [{}]", token);

AuthenticationInfo info;
try {
info = this.doAuthenticate(token);
if (info == null) {
String msg = "No account information found for authentication token [" + token + "] by this Authenticator instance. Please check that it is configured correctly.";
throw new AuthenticationException(msg);
}
} catch (Throwable var8) {
AuthenticationException ae = null;
if (var8 instanceof AuthenticationException) {
ae = (AuthenticationException)var8;
}

if (ae == null) {
String msg = "Authentication failed for token submission [" + token + "]. Possible unexpected error? (Typical or expected login exceptions should extend from AuthenticationException).";
ae = new AuthenticationException(msg, var8);
if (log.isWarnEnabled()) {
log.warn(msg, var8);
}
}

try {
this.notifyFailure(token, ae);
} catch (Throwable var7) {
if (log.isWarnEnabled()) {
String msg = "Unable to send notification for failed authentication attempt - listener error?. Please check your AuthenticationListener implementation(s). Logging sending exception and propagating original AuthenticationException instead...";
log.warn(msg, var7);
}
}

throw ae;
}

log.debug("Authentication successful for token [{}]. Returned account [{}]", token, info);
this.notifySuccess(token, info);
return info;
}
}public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
if (token == null) {
throw new IllegalArgumentException("Method argument (authentication token) cannot be null.");
} else {
log.trace("Authentication attempt received for token [{}]", token);

AuthenticationInfo info;
try {
info = this.doAuthenticate(token);
if (info == null) {
String msg = "No account information found for authentication token [" + token + "] by this Authenticator instance. Please check that it is configured correctly.";
throw new AuthenticationException(msg);
}
} catch (Throwable var8) {
AuthenticationException ae = null;
if (var8 instanceof AuthenticationException) {
ae = (AuthenticationException)var8;
}

if (ae == null) {
String msg = "Authentication failed for token submission [" + token + "]. Possible unexpected error? (Typical or expected login exceptions should extend from AuthenticationException).";
ae = new AuthenticationException(msg, var8);
if (log.isWarnEnabled()) {
log.warn(msg, var8);
}
}

try {
this.notifyFailure(token, ae);
} catch (Throwable var7) {
if (log.isWarnEnabled()) {
String msg = "Unable to send notification for failed authentication attempt - listener error?. Please check your AuthenticationListener implementation(s). Logging sending exception and propagating original AuthenticationException instead...";
log.warn(msg, var7);
}
}

throw ae;
}

log.debug("Authentication successful for token [{}]. Returned account [{}]", token, info);
this.notifySuccess(token, info);
return info;
}
}

/*ModularRealmAuthenticator */
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
this.assertRealmsConfigured();
Collection<Realm> realms = this.getRealms();
return realms.size() == 1 ? this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken);
}

/*AuthenticatingRealm*/
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info = this.getCachedAuthenticationInfo(token);
if (info == null) {
info = this.doGetAuthenticationInfo(token);
log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
if (token != null && info != null) {
this.cacheAuthenticationInfoIfPossible(token, info);
}
} else {
log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
}

if (info != null) {
this.assertCredentialsMatch(token, info);
} else {
log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
}

return info;
}

/*SimpleAccountRealm 用户名存在验证*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken)token;
SimpleAccount account = this.getUser(upToken.getUsername());
if (account != null) {
if (account.isLocked()) {
throw new LockedAccountException("Account [" + account + "] is locked.");
}

if (account.isCredentialsExpired()) {
String msg = "The credentials for account [" + account + "] are expired";
throw new ExpiredCredentialsException(msg);
}
}

return account;
}

rotected SimpleAccount getUser(String username) {
this.USERS_LOCK.readLock().lock();

SimpleAccount var2;
try {
var2 = (SimpleAccount)this.users.get(username);
} finally {
this.USERS_LOCK.readLock().unlock();
}

return var2;
}

/*AuthenticatingRealm*/
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info = this.getCachedAuthenticationInfo(token);
if (info == null) {
info = this.doGetAuthenticationInfo(token);
log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
if (token != null && info != null) {
this.cacheAuthenticationInfoIfPossible(token, info);
}
} else {
log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
}

if (info != null) {
this.assertCredentialsMatch(token, info);
} else {
log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
}

return info;
}

/*AuthenticatingRealm token封装用户帐号密码 AuthenticationInfo封装从数据库中查询出的帐号密码*/
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
CredentialsMatcher cm = this.getCredentialsMatcher();
if (cm != null) {
if (!cm.doCredentialsMatch(token, info)) {
String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
throw new IncorrectCredentialsException(msg);
}
} else {
throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify credentials during authentication. If you do not wish for credentials to be examined, you can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
}
}

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
public class SimpleAccountRealm extends AuthorizingRealm {
protected final Map<String, SimpleAccount> users;
protected final Map<String, SimpleRole> roles;
protected final ReadWriteLock USERS_LOCK;
protected final ReadWriteLock ROLES_LOCK;

.......

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken)token;
SimpleAccount account = this.getUser(upToken.getUsername());
if (account != null) {
if (account.isLocked()) {
throw new LockedAccountException("Account [" + account + "] is locked.");
}

if (account.isCredentialsExpired()) {
String msg = "The credentials for account [" + account + "] are expired";
throw new ExpiredCredentialsException(msg);
}
}

return account;
}

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = this.getUsername(principals);
this.USERS_LOCK.readLock().lock();

AuthorizationInfo var3;
try {
var3 = (AuthorizationInfo)this.users.get(username);
} finally {
this.USERS_LOCK.readLock().unlock();
}

return var3;
}
}
1
2
3
4
5
6
7
8
9
10
/**
* 授权Realm
* 继承AuthenticatingRealm
*/
public abstract class AuthorizingRealm extends AuthenticatingRealm implements Authorizer, Initializable, PermissionResolverAware, RolePermissionResolverAware {
......
protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection var1);
......

}
1
2
3
4
5
6
7
8
/**
* 认证Realm
*/
public abstract class AuthenticatingRealm extends CachingRealm implements Initializable {
......
protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken var1) throws AuthenticationException;
......
}

自定义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
package com.zero.shirodemo.realm;

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.realm.AuthorizingRealm;
import org.apache.shiro.realm.SimpleAccountRealm;
import org.apache.shiro.subject.PrincipalCollection;

/**
* 自定义Realm实现 将数据库实现作为认证/授权数据源
*/
public class MyRealm extends AuthorizingRealm {
/*授权*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/*认证 逻辑实现用户存在,而密码校验仍由Shiro实现*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
/*1.Token中获取用户名*/
String principal = (String) authenticationToken.getPrincipal();
/*2.根据身份信息数据库查询*/
if("root".equals(principal)){
/* 参数 数据库中用户名 数据库用户名对应密码 提供当前realm名字*/
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("root","ro",this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
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
public class MyRealmTestAuthenticator {
public static void main(String[] args) {
/*1.创建SecurityManager*/
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
/*2.设置自定义realm**/
defaultSecurityManager.setRealm(new MyRealm());
/*3.通过安全工具类设置安全管理器*/
SecurityUtils.setSecurityManager(defaultSecurityManager);
/*4.通过安全工具类获取Subject*/
Subject subject = SecurityUtils.getSubject();
/*5.创建Token*/
UsernamePasswordToken token = new UsernamePasswordToken("root","root");
try {
System.out.println("认证状态: "+subject.isAuthenticated());
subject.login(token);
System.out.println("认证状态: "+subject.isAuthenticated());
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("认证失败:当前用户不存在");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("认证失败:密码错误");
}
}
}

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
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
if (!realm.supports(token)) {
String msg = "Realm [" + realm + "] does not support authentication token [" + token + "]. Please ensure that the appropriate Realm implementation is configured correctly or that the realm accepts AuthenticationTokens of this type.";
throw new UnsupportedTokenException(msg);
} else {
AuthenticationInfo info = realm.getAuthenticationInfo(token); /*不存在用户info为null*/
if (info == null) {
String msg = "Realm [" + realm + "] was unable to find account data for the submitted AuthenticationToken [" + token + "].";
throw new UnknownAccountException(msg);
} else {
return info;
}
}
}


public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info = this.getCachedAuthenticationInfo(token);
if (info == null) {
info = this.doGetAuthenticationInfo(token); /*自定义Realm实现:用户名存在验证*/
log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
if (token != null && info != null) {
this.cacheAuthenticationInfoIfPossible(token, info);
}
} else {
log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
}

if (info != null) {
this.assertCredentialsMatch(token, info); /*1.如果用户存在密码校验*/
} else {
log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
}

return info;
}



protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
CredentialsMatcher cm = this.getCredentialsMatcher();
if (cm != null) {
if (!cm.doCredentialsMatch(token, info)) { /*2.密码校验*/
String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
throw new IncorrectCredentialsException(msg);
}
} else {
throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify credentials during authentication. If you do not wish for credentials to be examined, you can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
}
}

public CredentialsMatcher getCredentialsMatcher() {
return this.credentialsMatcher; /*credentialsMatcher:SimpleCredentialsMatcher*/
}


public class SimpleCredentialsMatcher extends CodecSupport implements CredentialsMatcher {
private static final Logger log = LoggerFactory.getLogger(SimpleCredentialsMatcher.class);

public SimpleCredentialsMatcher() {
}

protected Object getCredentials(AuthenticationToken token) {
return token.getCredentials();
}

protected Object getCredentials(AuthenticationInfo info) {
return info.getCredentials();
}

/*4.密码校验*/
protected boolean equals(Object tokenCredentials, Object accountCredentials) {
if (log.isDebugEnabled()) {
log.debug("Performing credentials equality check for tokenCredentials of type [" + tokenCredentials.getClass().getName() + " and accountCredentials of type [" + accountCredentials.getClass().getName() + "]");
}

if (this.isByteSource(tokenCredentials) && this.isByteSource(accountCredentials)) {
if (log.isDebugEnabled()) {
log.debug("Both credentials arguments can be easily converted to byte arrays. Performing array equals comparison");
}

byte[] tokenBytes = this.toBytes(tokenCredentials);
byte[] accountBytes = this.toBytes(accountCredentials);
return MessageDigest.isEqual(tokenBytes, accountBytes);
} else {
return accountCredentials.equals(tokenCredentials);
}
}

/*3.密码校验*/
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
Object tokenCredentials = this.getCredentials(token);
Object accountCredentials = this.getCredentials(info);
return this.equals(tokenCredentials, accountCredentials);
}
}

MD5 Salt

1.MD5讯息摘要演算法(MD5 Message-Digest Algorithm),一种被广泛使用的密码杂凑函数,可以产生出一个128位元(16个字元(BYTES))的散列值(hash value),用于确保信息传输完整一致。MD5由美国密码学家Ronald Linn Rivest设计,于1992年公开,用以取代MD4演算法。将数据(如一段文字)运算变为另一固定长度值,是杂凑算法的基础原理。2004年,证实MD5演算法无法防止碰撞攻击(英语:Collision_attack),因此不适用于安全性认证,如SSL公开金钥认证或是数位签章等用途。

2.盐(Salt),在密码学中,是指在散列之前将散列内容(如密码)的任意固定位置插入特定的字符串。这个在散列中加入字符串的方式称为"加盐"。其作用是让加盐后的散列结果和没有加盐的结果不相同,在不同的应用情景中,这个处理可以增加额外的安全性。

在大部分情况,盐是不需要保密的。盐可以是随机产生的字符串,其插入的位置可以也是随意而定。如果这个散列结果在将来需要进行验证(例如:验证用户输入的密码),则需要将已使用的盐记录下来。通常情况下,当字段经过散列处理(如SHA-1),会生成一段散列值,而散列后的值一般是无法通过特定算法得到原始字段的。但是某些情况,比如一个大型的彩虹表,通过在表中搜索该SHA-1值,很有可能在极短的时间内找到该散列值对应的真实字段内容。

加盐后的散列值,可以极大的降低由于用户数据被盗而带来的密码泄漏风险,即使通过彩虹表寻找到了散列后的数值所对应的原始内容,但是由于经过了加盐,插入的字符串扰乱了真正的密码,使得获得真实密码的概率大大降低。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ShiroMD5Demo {
public static void main(String[] args) {
/*
创建md5
Md5Hash md5hash = new Md5Hash();
md5Hash.setBytes("123".getBytes());
System.out.println(md5Hash.toHex());
*/
/*MD5*/
Md5Hash md5Hash = new Md5Hash("12345678");
System.out.println(md5Hash.toHex());
/*MD5 + Salt*/
Md5Hash md5Hash1 = new Md5Hash("12345678","ui-342");
System.out.println(md5Hash1.toHex());
/*MD5 + Salt +Hash散列*/
Md5Hash md5Hash2 = new Md5Hash("12345678","ui-342",1024);
System.out.println(md5Hash2.toHex());
}
}
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
public class MyMD5RealmTestAuthenticator {
public static void main(String[] args) {
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
MyMD5Realm realm = new MyMD5Realm();
/*设置Realm使用HashedCredentialsMatcher 而非默认的SimpleCredentialsMatcher*/
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
/*设置散列算法*/
credentialsMatcher.setHashAlgorithmName("md5");
/*设置散列次数*/
credentialsMatcher.setHashIterations(1024);
realm.setCredentialsMatcher(credentialsMatcher);

defaultSecurityManager.setRealm(realm);

SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("root","12345678");
try {
subject.login(token);
System.out.println("认证状态: "+subject.isAuthenticated());
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("认证失败:当前用户不存在");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("认证失败:密码错误");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyMD5Realm extends AuthorizingRealm {

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String principal =(String) authenticationToken.getPrincipal();
if("root".equals(principal)){
return new SimpleAuthenticationInfo(principal,"e5a110a450d04391d604a94b739ca9ad", ByteSource.Util.bytes("ui-342"),this.getName());

}
return null;
}
}


授权

1.关键对象:

  • 主体(Subject):访问应用的用户。
  • 资源(Resource):在应用中用户可以访问的URL,如访问页面、查看或编辑数据、访问业务方法等等都是资源。用户只要授权后才能访问。
  • 权限(Permission):安全策略中的原子授权单位,规定了主体对资源的操作许可,通过权限可以表示在应用中用户有没有操作某个资源的权力。

2.授权方式

  • 基于角色的访问控制(Role-Based Access Control)是以角色为中心访问控制

    1
    2
    3
    if(subject.hasRole("admin")){
    /*操作资源*/
    }
  • 基于资源的访问控制(Resource-Based Access Control)是以资源为中心访问控制

    1
    2
    3
    4
    5
    6
    7
    8
    /*资源实例*/
    if(subject.isPermission("user:update:01")){
    /*对01用户具有修改权限*/
    }
    /*资源类型*/
    if(subject.isPermission("user:update:*")){
    /*对用户具有修改权限*/
    }
  1. 权限字符串: 资源标识符:操作:资源实例标识符,对哪个资源的哪个实例具有什么操作(: 分割符,* 通配符)
    • 用户创建权限 user:createuser:create:\*
    • 用户修改实例001的权限:user:update:001
    • 用户实例001的所有权限:user:\*:001

Shiro中授权实现:

1.编程式

1
2
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {} else {}

2.注解式

1
2
@RequiresRoles("admin")
public void selectAllUser() {}

3.标签式

1
2
3
4
5
<!--JSP/GSP 标签-->
<shiro:hasRole name="admin">
<!--有权限 -->
</shiro:hasRole>
注意:

Thymeleaf官方没有提供Shiro的标签,需要引入第三方实现: thymeleaf-extras-shiro


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
public class MyRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
/*System.out.println("调用doGetAuthorizationInfo方法");*/
/*principalCollection/principals获取主身份信息*/
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
System.out.println("身份信息:"+primaryPrincipal);
/*根据身份信息(用户名)获取当前用户的角色信息以及权限信息*/
SimpleAuthorizationInfo simpleAuthenticationInfo = new SimpleAuthorizationInfo();
/*权限对象赋值数据库角色信息*/
simpleAuthenticationInfo.addRole("admin");
simpleAuthenticationInfo.addRole("user");
/*权限对象赋值数据库权限信息*/
simpleAuthenticationInfo.addStringPermission("user:*:01");
simpleAuthenticationInfo.addStringPermission("product:create");
return simpleAuthenticationInfo;
}

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String principal = (String) authenticationToken.getPrincipal();
if("root".equals(principal)){
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("root","12345678",this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
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
public class MyRealmTestAuthenticator {
public static void main(String[] args) {
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(new MyRealm());
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("root","12345678");
try {
subject.login(token);
System.out.println("认证状态: "+subject.isAuthenticated());
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("认证失败:当前用户不存在");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("认证失败:密码错误");
}

/*认证用户进行授权*/
if(subject.isAuthenticated()){
/*1.基于角色权限控制*/
System.out.println(subject.hasRole("admin"));
System.out.println(subject.hasRole("user"));

/*2.基于多角色权限控制*/
System.out.println(subject.hasAllRoles(Arrays.asList("admin","super")));

for(boolean aBoolean: subject.hasRoles(Arrays.asList("admin","super","user"))){
System.out.println(aBoolean);
}

/*3.基于权限字符串(资源标识符:操作:资源类型)的访问控制*/
System.out.println(subject.isPermitted("user:*:01"));
System.out.println(subject.isPermitted("user:update:01"));
System.out.println(subject.isPermitted("product:create:02"));


/*3.基于多个权限字符串(资源标识符:操作:资源类型)的访问控制*/
for(boolean b: subject.isPermitted("user:*:01","product:select")){
System.out.println(b);
}
System.out.println(subject.isPermittedAll("user:*:01","product:delete"));


}
}
}

SpringBoot集成

Shiro SpringBoot


1.环境搭建

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
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--引入JSP依赖-->
<!-- https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-jasper -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/jstl/jstl -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>

<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.9.0</version>
</dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
server:
port: 8080
servlet:
context-path: /shiro

spring:
application: /shiro
mvc:
view:
prefix: /
suffix: .jsp

1.SpringMVC+Shiro初次访问URL出现jsessionid 报400 Bad Request错误:

Set to true to disable support for using URL rewriting to track session IDs for clients of this Context. URL rewriting is an optional component of the servlet 2.5 specification but disabling URL rewriting will result in non-compliant behaviour since the specification requires that there must be a way to retain sessions if the client doesn’t allow session cookies. If not specified, the specification compliant default value of false will be used.

  • 修改配置文件
1
2
server.servlet.session.tracking-modes=cookie
server.servlet.session.cookie.http-only=true
  • 修改Shiro配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 解决shiro的初次访问URL出现jsessionid 报400 Bad Request错误
*/
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}

@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(realm);
defaultWebSecurityManager.setSessionManager(sessionManager());
/*
设置cookie管理器
securityManager.setRememberMeManager(rememberMeManager());
*/
return defaultWebSecurityManager;
}

过滤器

Filter Name Class
anon AnonymousFilter
authc FormAuthenticationFilter
authcBasic BasicHttpAuthenticationFilter
authcBearer BearerHttpAuthenticationFilter
invalidRequest InvalidRequestFilter
logout LogoutFilter
noSessionCreation NoSessionCreationFilter
perms PermissionsAuthorizationFilter
port PortFilter
rest HttpMethodPermissionFilter
roles RolesAuthorizationFilter
ssl SslFilter
user UserFilter