原文地址,转载请注明出处: https://blog.csdn.net/qq_34021712/article/details/80791219 © 王赛超 css
以前写过一篇博客,使用的一个开源项目,实现了redis做为缓存 缓存用户的权限 和 session信息,还有两个功能没有修改,一个是用户并发登陆限制,一个是用户密码错误次数.本篇中几个类 也是使用的开源项目中的类,只不过是拿出来了,redis单独作的配置,方便进行优化。html
整合过程
1.首先是整合Redis
Redis客户端使用的是RedisTemplate,本身写了一个序列化工具继承RedisSerializer前端
SerializeUtils.java
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
package com.springboot.test.shiro.global.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import java.io.*;
/** * @author : wangsaichao * @date : 2018/6/20 * @description : redis的value序列化工具 */
public class SerializeUtils implements RedisSerializer {
private static Logger logger = LoggerFactory.getLogger(SerializeUtils.class);
public static boolean isEmpty (
byte [] data) {
return (data ==
null || data.length ==
0 );
}
/** * 序列化 * @param object * @return * @throws SerializationException */
@Override
public byte []
serialize (Object object)
throws SerializationException {
byte [] result =
null ;
if (object ==
null ) {
return new byte [
0 ];
}
try (
ByteArrayOutputStream byteStream =
new ByteArrayOutputStream(
128 );
ObjectOutputStream objectOutputStream =
new ObjectOutputStream(byteStream)
){
if (!(object
instanceof Serializable)) {
throw new IllegalArgumentException(SerializeUtils.class.getSimpleName() +
" requires a Serializable payload " +
"but received an object of type [" + object.getClass().getName() +
"]" );
}
objectOutputStream.writeObject(object);
objectOutputStream.flush();
result = byteStream.toByteArray();
}
catch (Exception ex) {
logger.error(
"Failed to serialize" ,ex);
}
return result;
}
/** * 反序列化 * @param bytes * @return * @throws SerializationException */
@Override
public Object
deserialize (
byte [] bytes)
throws SerializationException {
Object result =
null ;
if (isEmpty(bytes)) {
return null ;
}
try (
ByteArrayInputStream byteStream =
new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream =
new ObjectInputStream(byteStream)
){
result = objectInputStream.readObject();
}
catch (Exception e) {
logger.error(
"Failed to deserialize" ,e);
}
return result;
}
}
RedisConfig.java
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
package com.springboot.test.shiro.config;
import com.springboot.test.shiro.global.utils.SerializeUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;
/** * @author : wangsaichao * @date : 2017/11/23 * @description : redis配置 */
@Configuration
public class RedisConfig {
/** * redis地址 */
@Value (
"${spring.redis.host}" )
private String host;
/** * redis端口号 */
@Value (
"${spring.redis.port}" )
private Integer port;
/** * redis密码 */
@Value (
"${spring.redis.password}" )
private String password;
/** * JedisPoolConfig 链接池 * @return */
@Bean
public JedisPoolConfig
jedisPoolConfig (){
JedisPoolConfig jedisPoolConfig=
new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(
300 );
jedisPoolConfig.setMaxTotal(
1000 );
jedisPoolConfig.setMaxWaitMillis(
1000 );
jedisPoolConfig.setMinEvictableIdleTimeMillis(
300000 );
jedisPoolConfig.setNumTestsPerEvictionRun(
10 );
jedisPoolConfig.setTimeBetweenEvictionRunsMillis(
30000 );
jedisPoolConfig.setTestOnBorrow(
true );
jedisPoolConfig.setTestWhileIdle(
true );
return jedisPoolConfig;
}
/** * 配置工厂 * @param jedisPoolConfig * @return */
@Bean
public JedisConnectionFactory
jedisConnectionFactory (JedisPoolConfig jedisPoolConfig){
JedisConnectionFactory jedisConnectionFactory=
new JedisConnectionFactory();
jedisConnectionFactory.setPoolConfig(jedisPoolConfig);
jedisConnectionFactory.setHostName(host);
jedisConnectionFactory.setPort(port);
jedisConnectionFactory.setPassword(password);
jedisConnectionFactory.setTimeout(
5000 );
return jedisConnectionFactory;
}
/** * shiro redis缓存使用的模板 * 实例化 RedisTemplate 对象 * @return */
@Bean (
"shiroRedisTemplate" )
public RedisTemplate
shiroRedisTemplate (RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate =
new RedisTemplate();
redisTemplate.setKeySerializer(
new StringRedisSerializer());
redisTemplate.setHashKeySerializer(
new StringRedisSerializer());
redisTemplate.setHashValueSerializer(
new SerializeUtils());
redisTemplate.setValueSerializer(
new SerializeUtils());
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
}
RedisManager.java
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
package com.springboot.test.shiro.config.shiro;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.*;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.concurrent.TimeUnit;
/** * * @author wangsaichao * 基于spring和redis的redisTemplate工具类 */
public class RedisManager {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/** * 指定缓存失效时间 * @param key 键 * @param time 时间(秒) */
public void expire (String key,
long time){
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
/** * 判断key是否存在 * @param key 键 * @return true 存在 false不存在 */
public Boolean
hasKey (String key){
return redisTemplate.hasKey(key);
}
/** * 删除缓存 * @param key 能够传一个值 或多个 */
@SuppressWarnings (
"unchecked" )
public void del (String ... key){
if (key!=
null &&key.length>
0 ){
if (key.length==
1 ){
redisTemplate.delete(key[
0 ]);
}
else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
/** * 批量删除key * @param keys */
public void del (Collection keys){
redisTemplate.delete(keys);
}
/** * 普通缓存获取 * @param key 键 * @return 值 */
public Object
get (String key){
return redisTemplate.opsForValue().get(key);
}
/** * 普通缓存放入 * @param key 键 * @param value 值 */
public void set (String key,Object value) {
redisTemplate.opsForValue().set(key, value);
}
/** * 普通缓存放入并设置时间 * @param key 键 * @param value 值 * @param time 时间(秒) time要大于0 若是time小于等于0 将设置无限期 */
public void set (String key,Object value,
long time){
if (time>
0 ){
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
}
else {
set(key, value);
}
}
/** * 使用scan命令 查询某些前缀的key * @param key * @return */
public Set<String>
scan (String key){
Set<String> execute =
this .redisTemplate.execute(
new RedisCallback<Set<String>>() {
@Override
public Set<String>
doInRedis (RedisConnection connection)
throws DataAccessException {
Set<String> binaryKeys =
new HashSet<>();
Cursor<
byte []> cursor = connection.scan(
new ScanOptions.ScanOptionsBuilder().match(key).count(
1000 ).build());
while (cursor.hasNext()) {
binaryKeys.add(
new String(cursor.next()));
}
return binaryKeys;
}
});
return execute;
}
/** * 使用scan命令 查询某些前缀的key 有多少个 * 用来获取当前session数量,也就是在线用户 * @param key * @return */
public Long
scanSize (String key){
long dbSize =
this .redisTemplate.execute(
new RedisCallback<Long>() {
@Override
public Long
doInRedis (RedisConnection connection)
throws DataAccessException {
long count =
0 L;
Cursor<
byte []> cursor = connection.scan(ScanOptions.scanOptions().match(key).count(
1000 ).build());
while (cursor.hasNext()) {
cursor.next();
count++;
}
return count;
}
});
return dbSize;
}
}
2.使用Redis做为缓存须要shiro重写cache、cacheManager、SessionDAO
RedisCache.java
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
254
package com.springboot.test.shiro.config.shiro;
import com.springboot.test.shiro.global.exceptions.PrincipalIdNullException;
import com.springboot.test.shiro.global.exceptions.PrincipalInstanceException;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
/** * @author : wangsaichao * @date : 2018/6/22 * @description : 参考 shiro-redis 开源项目 Git地址 https://github.com/alexxiyang/shiro-redis */
public class RedisCache <K , V > implements Cache <K , V > {
private static Logger logger = LoggerFactory.getLogger(RedisCache.class);
private RedisManager redisManager;
private String keyPrefix =
"" ;
private int expire =
0 ;
private String principalIdFieldName = RedisCacheManager.DEFAULT_PRINCIPAL_ID_FIELD_NAME;
/** * Construction * @param redisManager */
public RedisCache (RedisManager redisManager, String prefix,
int expire, String principalIdFieldName) {
if (redisManager ==
null ) {
throw new IllegalArgumentException(
"redisManager cannot be null." );
}
this .redisManager = redisManager;
if (prefix !=
null && !
"" .equals(prefix)) {
this .keyPrefix = prefix;
}
if (expire != -
1 ) {
this .expire = expire;
}
if (principalIdFieldName !=
null && !
"" .equals(principalIdFieldName)) {
this .principalIdFieldName = principalIdFieldName;
}
}
@Override
public V
get (K key)
throws CacheException {
logger.debug(
"get key [{}]" ,key);
if (key ==
null ) {
return null ;
}
try {
String redisCacheKey = getRedisCacheKey(key);
Object rawValue = redisManager.get(redisCacheKey);
if (rawValue ==
null ) {
return null ;
}
V value = (V) rawValue;
return value;
}
catch (Exception e) {
throw new CacheException(e);
}
}
@Override
public V
put (K key, V value)
throws CacheException {
logger.debug(
"put key [{}]" ,key);
if (key ==
null ) {
logger.warn(
"Saving a null key is meaningless, return value directly without call Redis." );
return value;
}
try {
String redisCacheKey = getRedisCacheKey(key);
redisManager.set(redisCacheKey, value !=
null ? value :
null , expire);
return value;
}
catch (Exception e) {
throw new CacheException(e);
}
}
@Override
public V
remove (K key)
throws CacheException {
logger.debug(
"remove key [{}]" ,key);
if (key ==
null ) {
return null ;
}
try {
String redisCacheKey = getRedisCacheKey(key);
Object rawValue = redisManager.get(redisCacheKey);
V previous = (V) rawValue;
redisManager.del(redisCacheKey);
return previous;
}
catch (Exception e) {
throw new CacheException(e);
}
}
private String
getRedisCacheKey (K key) {
if (key ==
null ) {
return null ;
}
return this .keyPrefix + getStringRedisKey(key);
}
private String
getStringRedisKey (K key) {
String redisKey;
if (key
instanceof PrincipalCollection) {
redisKey = getRedisKeyFromPrincipalIdField((PrincipalCollection) key);
}
else {
redisKey = key.toString();
}
return redisKey;
}
private String
getRedisKeyFromPrincipalIdField (PrincipalCollection key) {
String redisKey;
Object principalObject = key.getPrimaryPrincipal();
Method pincipalIdGetter =
null ;
Method[] methods = principalObject.getClass().getDeclaredMethods();
for (Method m:methods) {
if (RedisCacheManager.DEFAULT_PRINCIPAL_ID_FIELD_NAME.equals(
this .principalIdFieldName)
&& (
"getAuthCacheKey" .equals(m.getName()) ||
"getId" .equals(m.getName()))) {
pincipalIdGetter = m;
break ;
}
if (m.getName().equals(
"get" +
this .principalIdFieldName.substring(
0 ,
1 ).toUpperCase() +
this .principalIdFieldName.substring(
1 ))) {
pincipalIdGetter = m;
break ;
}
}
if (pincipalIdGetter ==
null ) {
throw new PrincipalInstanceException(principalObject.getClass(),
this .principalIdFieldName);
}
try {
Object idObj = pincipalIdGetter.invoke(principalObject);
if (idObj ==
null ) {
throw new PrincipalIdNullException(principalObject.getClass(),
this .principalIdFieldName);
}
redisKey = idObj.toString();
}
catch (IllegalAccessException e) {
throw new PrincipalInstanceException(principalObject.getClass(),
this .principalIdFieldName, e);
}
catch (InvocationTargetException e) {
throw new PrincipalInstanceException(principalObject.getClass(),
this .principalIdFieldName, e);
}
return redisKey;
}
@Override
public void clear ()
throws CacheException {
logger.debug(
"clear cache" );
Set<String> keys =
null ;
try {
keys = redisManager.scan(
this .keyPrefix +
"*" );
}
catch (Exception e) {
logger.error(
"get keys error" , e);
}
if (keys ==
null || keys.size() ==
0 ) {
return ;
}
for (String key: keys) {
redisManager.del(key);
}
}
@Override
public int size () {
Long longSize =
0 L;
try {
longSize =
new Long(redisManager.scanSize(
this .keyPrefix +
"*" ));
}
catch (Exception e) {
logger.error(
"get keys error" , e);
}
return longSize.intValue();
}
@SuppressWarnings (
"unchecked" )
@Override
public Set<K>
keys () {
Set<String> keys =
null ;
try {
keys = redisManager.scan(
this .keyPrefix +
"*" );
}
catch (Exception e) {
logger.error(
"get keys error" , e);
return Collections.emptySet();
}
if (CollectionUtils.isEmpty(keys)) {
return Collections.emptySet();
}
Set<K> convertedKeys =
new HashSet<K>();
for (String key:keys) {
try {
convertedKeys.add((K) key);
}
catch (Exception e) {
logger.error(
"deserialize keys error" , e);
}
}
return convertedKeys;
}
@Override
public Collection<V>
values () {
Set<String> keys =
null ;
try {
keys = redisManager.scan(
this .keyPrefix +
"*" );
}
catch (Exception e) {
logger.error(
"get values error" , e);
return Collections.emptySet();
}
if (CollectionUtils.isEmpty(keys)) {
return Collections.emptySet();
}
List<V> values =
new ArrayList<V>(keys.size());
for (String key : keys) {
V value =
null ;
try {
value = (V) redisManager.get(key);
}
catch (Exception e) {
logger.error(
"deserialize values= error" , e);
}
if (value !=
null ) {
values.add(value);
}
}
return Collections.unmodifiableList(values);
}
public String
getKeyPrefix () {
return keyPrefix;
}
public void setKeyPrefix (String keyPrefix) {
this .keyPrefix = keyPrefix;
}
public String
getPrincipalIdFieldName () {
return principalIdFieldName;
}
public void setPrincipalIdFieldName (String principalIdFieldName) {
this .principalIdFieldName = principalIdFieldName;
}
}
getRedisKeyFromPrincipalIdField()
是获取缓存的用户身份信息 和用户权限信息。 里面有一个属性principalIdFieldName 在RedisCacheManager也有这个属性,设置其中一个就能够.是为了给缓存用户身份和权限信息在Redis中的key惟一,登陆用户名多是username 或者 phoneNum 或者是Email中的一个,如 个人User实体类中 有一个 usernane字段,也是登陆时候使用的用户名,在redis中缓存的权限信息key 以下, 这个admin 就是 经过getUsername得到的。 java
读取用户权限信息时,还用到两个异常类,以下:
PrincipalInstanceException.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.springboot.test.shiro.global.exceptions;
/** * @author : wangsaichao * @date : 2018/6/21 * @description : */
public class PrincipalInstanceException extends RuntimeException {
private static final String MESSAGE =
"We need a field to identify this Cache Object in Redis. "
+
"So you need to defined an id field which you can get unique id to identify this principal. "
+
"For example, if you use UserInfo as Principal class, the id field maybe userId, userName, email, etc. "
+
"For example, getUserId(), getUserName(), getEmail(), etc.\n"
+
"Default value is authCacheKey or id, that means your principal object has a method called \"getAuthCacheKey()\" or \"getId()\"" ;
public PrincipalInstanceException (Class clazz, String idMethodName) {
super (clazz +
" must has getter for field: " + idMethodName +
"\n" + MESSAGE);
}
public PrincipalInstanceException (Class clazz, String idMethodName, Exception e) {
super (clazz +
" must has getter for field: " + idMethodName +
"\n" + MESSAGE, e);
}
}
PrincipalIdNullException.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.springboot.test.shiro.global.exceptions;
/** * @author : wangsaichao * @date : 2018/6/21 * @description : */
public class PrincipalIdNullException extends RuntimeException {
private static final String MESSAGE =
"Principal Id shouldn't be null!" ;
public PrincipalIdNullException (Class clazz, String idMethodName) {
super (clazz +
" id field: " + idMethodName +
", value is null\n" + MESSAGE);
}
}
RedisCacheManager.java
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
package com.springboot.test.shiro.config.shiro;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/** * @author : wangsaichao * @date : 2018/6/22 * @description : 参考 shiro-redis 开源项目 Git地址 https://github.com/alexxiyang/shiro-redis */
public class RedisCacheManager implements CacheManager {
private final Logger logger = LoggerFactory.getLogger(RedisCacheManager.class);
/** * fast lookup by name map */
private final ConcurrentMap<String, Cache> caches =
new ConcurrentHashMap<String, Cache>();
private RedisManager redisManager;
/** * expire time in seconds */
private static final int DEFAULT_EXPIRE =
1800 ;
private int expire = DEFAULT_EXPIRE;
/** * The Redis key prefix for caches */
public static final String DEFAULT_CACHE_KEY_PREFIX =
"shiro:cache:" ;
private String keyPrefix = DEFAULT_CACHE_KEY_PREFIX;
public static final String DEFAULT_PRINCIPAL_ID_FIELD_NAME =
"authCacheKey or id" ;
private String principalIdFieldName = DEFAULT_PRINCIPAL_ID_FIELD_NAME;
@Override
public <K, V> Cache<K, V>
getCache (String name)
throws CacheException {
logger.debug(
"get cache, name={}" ,name);
Cache cache = caches.get(name);
if (cache ==
null ) {
cache =
new RedisCache<K, V>(redisManager,keyPrefix + name +
":" , expire, principalIdFieldName);
caches.put(name, cache);
}
return cache;
}
public RedisManager
getRedisManager () {
return redisManager;
}
public void setRedisManager (RedisManager redisManager) {
this .redisManager = redisManager;
}
public String
getKeyPrefix () {
return keyPrefix;
}
public void setKeyPrefix (String keyPrefix) {
this .keyPrefix = keyPrefix;
}
public int getExpire () {
return expire;
}
public void setExpire (
int expire) {
this .expire = expire;
}
public String
getPrincipalIdFieldName () {
return principalIdFieldName;
}
public void setPrincipalIdFieldName (String principalIdFieldName) {
this .principalIdFieldName = principalIdFieldName;
}
}
RedisSessionDAO.java
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
package com.springboot.test.shiro.config.shiro;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.ValidatingSession;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.util.*;
/** * @author : wangsaichao * @date : 2018/6/22 * @description : 参考 shiro-redis 开源项目 Git地址 https://github.com/alexxiyang/shiro-redis */
public class RedisSessionDAO extends AbstractSessionDAO {
private static Logger logger = LoggerFactory.getLogger(RedisSessionDAO.class);
private static final String DEFAULT_SESSION_KEY_PREFIX =
"shiro:session:" ;
private String keyPrefix = DEFAULT_SESSION_KEY_PREFIX;
private static final long DEFAULT_SESSION_IN_MEMORY_TIMEOUT =
1000 L;
/** * doReadSession be called about 10 times when login. * Save Session in ThreadLocal to resolve this problem. sessionInMemoryTimeout is expiration of Session in ThreadLocal. * The default value is 1000 milliseconds (1s). * Most of time, you don't need to change it. */
private long sessionInMemoryTimeout = DEFAULT_SESSION_IN_MEMORY_TIMEOUT;
/** * expire time in seconds */
private static final int DEFAULT_EXPIRE = -
2 ;
private static final int NO_EXPIRE = -
1 ;
/** * Please make sure expire is longer than sesion.getTimeout() */
private int expire = DEFAULT_EXPIRE;
private static final int MILLISECONDS_IN_A_SECOND =
1000 ;
private RedisManager redisManager;
private static ThreadLocal sessionsInThread =
new ThreadLocal();
@Override
public void update (Session session)
throws UnknownSessionException {
try {
if (session
instanceof ValidatingSession && !((ValidatingSession) session).isValid()) {
return ;
}
if (session
instanceof ShiroSession) {
ShiroSession ss = (ShiroSession) session;
if (!ss.isChanged()) {
return ;
}
ss.setChanged(
false );
}
this .saveSession(session);
}
catch (Exception e) {
logger.warn(
"update Session is failed" , e);
}
}
/** * save session * @param session * @throws UnknownSessionException */
private void saveSession (Session session)
throws UnknownSessionException {
if (session ==
null || session.getId() ==
null ) {
logger.error(
"session or session id is null" );
throw new UnknownSessionException(
"session or session id is null" );
}
String key = getRedisSessionKey(session.getId());
if (expire == DEFAULT_EXPIRE) {
this .redisManager.set(key, session, (
int ) (session.getTimeout() / MILLISECONDS_IN_A_SECOND));
return ;
}
if (expire != NO_EXPIRE && expire * MILLISECONDS_IN_A_SECOND < session.getTimeout()) {
logger.warn(
"Redis session expire time: "
+ (expire * MILLISECONDS_IN_A_SECOND)
+
" is less than Session timeout: "
+ session.getTimeout()
+
" . It may cause some problems." );
}
this .redisManager.set(key, session, expire);
}
@Override
public void delete (Session session) {
if (session ==
null || session.getId() ==
null ) {
logger.error(
"session or session id is null" );
return ;
}
try {
redisManager.del(getRedisSessionKey(session.getId()));
}
catch (Exception e) {
logger.error(
"delete session error. session id= {}" ,session.getId());
}
}
@Override
public Collection<Session>
getActiveSessions () {
Set<Session> sessions =
new HashSet<Session>();
try {
Set<String> keys = redisManager.scan(
this .keyPrefix +
"*" );
if (keys !=
null && keys.size() >
0 ) {
for (String key:keys) {
Session s = (Session) redisManager.get(key);
sessions.add(s);
}
}
}
catch (Exception e) {
logger.error(
"get active sessions error." );
}
return sessions;
}
public Long
getActiveSessionsSize () {
Long size =
0 L;
try {
size = redisManager.scanSize(
this .keyPrefix +
"*" );
}
catch (Exception e) {
logger.error(
"get active sessions error." );
}
return size;
}
@Override
protected Serializable
doCreate (Session session) {
if (session ==
null ) {
logger.error(
"session is null" );
throw new UnknownSessionException(
"session is null" );
}
Serializable sessionId =
this .generateSessionId(session);
this .assignSessionId(session, sessionId);
this .saveSession(session);
return sessionId;
}
@Override
protected Session
doReadSession (Serializable sessionId) {
if (sessionId ==
null ) {
logger.warn(
"session id is null" );
return null ;
}
Session s = getSessionFromThreadLocal(sessionId);
if (s !=
null ) {
return s;
}
logger.debug(
"read session from redis" );
try {
s = (Session) redisManager.get(getRedisSessionKey(sessionId));
setSessionToThreadLocal(sessionId, s);
}
catch (Exception e) {
logger.error(
"read session error. settionId= {}" ,sessionId);
}
return s;
}
private void setSessionToThreadLocal (Serializable sessionId, Session s) {
Map<Serializable, SessionInMemory> sessionMap = (Map<Serializable, SessionInMemory>) sessionsInThread.get();
if (sessionMap ==
null ) {
sessionMap =
new HashMap<Serializable, SessionInMemory>();
sessionsInThread.set(sessionMap);
}
SessionInMemory sessionInMemory =
new SessionInMemory();
sessionInMemory.setCreateTime(
new Date());
sessionInMemory.setSession(s);
sessionMap.put(sessionId, sessionInMemory);
}
private Session
getSessionFromThreadLocal (Serializable sessionId) {
Session s =
null ;
if (sessionsInThread.get() ==
null ) {
return null ;
}
Map<Serializable, SessionInMemory> sessionMap = (Map<Serializable, SessionInMemory>) sessionsInThread.get();
SessionInMemory sessionInMemory = sessionMap.get(sessionId);
if (sessionInMemory ==
null ) {
return null ;
}
Date now =
new Date();
long duration = now.getTime() - sessionInMemory.getCreateTime().getTime();
if (duration < sessionInMemoryTimeout) {
s = sessionInMemory.getSession();
logger.debug(
"read session from memory" );
}
else {
sessionMap.remove(sessionId);
}
return s;
}
private String
getRedisSessionKey (Serializable sessionId) {
return this .keyPrefix + sessionId;
}
public RedisManager
getRedisManager () {
return redisManager;
}
public void setRedisManager (RedisManager redisManager) {
this .redisManager = redisManager;
}
public String
getKeyPrefix () {
return keyPrefix;
}
public void setKeyPrefix (String keyPrefix) {
this .keyPrefix = keyPrefix;
}
public long getSessionInMemoryTimeout () {
return sessionInMemoryTimeout;
}
public void setSessionInMemoryTimeout (
long sessionInMemoryTimeout) {
this .sessionInMemoryTimeout = sessionInMemoryTimeout;
}
public int getExpire () {
return expire;
}
public void setExpire (
int expire) {
this .expire = expire;
}
}
3.Shiro配置
ShiroConfig.java
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
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
package com.springboot.test.shiro.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.springboot.test.shiro.config.shiro.*;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.session.mgt.eis.SessionIdGenerator;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.web.servlet.ErrorPage;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Properties;
/** * @author : wangsaichao * @date : 2018/5/10 * @description : Shiro配置 */
@Configuration
public class ShiroConfig {
/** * ShiroFilterFactoryBean 处理拦截资源文件问题。 * 注意:初始化ShiroFilterFactoryBean的时候须要注入:SecurityManager * Web应用中,Shiro可控制的Web请求必须通过Shiro主过滤器的拦截 * @param securityManager * @return */
@Bean (name =
"shirFilter" )
public ShiroFilterFactoryBean
shiroFilter (@
Qualifier ("securityManager") SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean =
new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl(
"/" );
shiroFilterFactoryBean.setSuccessUrl(
"/index" );
shiroFilterFactoryBean.setUnauthorizedUrl(
"/unauthorized" );
LinkedHashMap<String, Filter> filtersMap =
new LinkedHashMap<>();
filtersMap.put(
"kickout" , kickoutSessionControlFilter());
shiroFilterFactoryBean.setFilters(filtersMap);
LinkedHashMap<String, String> filterChainDefinitionMap =
new LinkedHashMap<>();
filterChainDefinitionMap.put(
"/login" ,
"anon" );
filterChainDefinitionMap.put(
"/" ,
"anon" );
filterChainDefinitionMap.put(
"/css/**" ,
"anon" );
filterChainDefinitionMap.put(
"/js/**" ,
"anon" );
filterChainDefinitionMap.put(
"/img/**" ,
"anon" );
filterChainDefinitionMap.put(
"/druid/**" ,
"anon" );
filterChainDefinitionMap.put(
"/unlockAccount" ,
"anon" );
filterChainDefinitionMap.put(
"/Captcha.jpg" ,
"anon" );
filterChainDefinitionMap.put(
"/logout" ,
"logout" );
filterChainDefinitionMap.put(
"/**" ,
"kickout,user" );
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/** * 配置核心安全事务管理器 * @return */
@Bean (name=
"securityManager" )
public SecurityManager
securityManager () {
DefaultWebSecurityManager securityManager =
new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm());
securityManager.setRememberMeManager(rememberMeManager());
securityManager.setCacheManager(cacheManager());
securityManager.setSessionManager(sessionManager());
return securityManager;
}
/** * 配置Shiro生命周期处理器 * @return */
@Bean (name =
"lifecycleBeanPostProcessor" )
public LifecycleBeanPostProcessor
lifecycleBeanPostProcessor () {
return new LifecycleBeanPostProcessor();
}
/** * 身份认证realm; (这个须要本身写,帐号密码校验;权限等) * @return */
@Bean
public ShiroRealm
shiroRealm (){
ShiroRealm shiroRealm =
new ShiroRealm();
shiroRealm.setCachingEnabled(
true );
shiroRealm.setAuthenticationCachingEnabled(
true );
shiroRealm.setAuthenticationCacheName(
"authenticationCache" );
shiroRealm.setAuthorizationCachingEnabled(
true );
shiroRealm.setAuthorizationCacheName(
"authorizationCache" );
shiroRealm.setCredentialsMatcher(retryLimitHashedCredentialsMatcher());
return shiroRealm;
}
/** * 必须(thymeleaf页面使用shiro标签控制按钮 是否显示) * 未引入thymeleaf包,Caused by: java.lang.ClassNotFoundException: org.thymeleaf.dialect.AbstractProcessorDialect * @return */
@Bean
public ShiroDialect
shiroDialect () {
return new ShiroDialect();
}
/** * 开启shiro 注解模式 * 能够在controller中的方法前加上注解 * 如 @RequiresPermissions ("userInfo:add") * @param securityManager * @return */
@Bean
public AuthorizationAttributeSourceAdvisor
authorizationAttributeSourceAdvisor (@
Qualifier ("securityManager") SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =
new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/** * 解决: 无权限页面不跳转 shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized") 无效 * shiro的源代码ShiroFilterFactoryBean.Java定义的filter必须知足filter instanceof AuthorizationFilter, * 只有perms,roles,ssl,rest,port才是属于AuthorizationFilter,而anon,authcBasic,auchc,user是AuthenticationFilter, * 因此unauthorizedUrl设置后页面不跳转 Shiro注解模式下,登陆失败与没有权限都是经过抛出异常。 * 而且默认并无去处理或者捕获这些异常。在SpringMVC下须要配置捕获相应异常来通知用户信息 * @return */
@Bean
public SimpleMappingExceptionResolver
simpleMappingExceptionResolver () {
SimpleMappingExceptionResolver simpleMappingExceptionResolver=
new SimpleMappingExceptionResolver();
Properties properties=
new Properties();
properties.setProperty(
"org.apache.shiro.authz.UnauthorizedException" ,
"/unauthorized" );
properties.setProperty(
"org.apache.shiro.authz.UnauthenticatedException" ,
"/unauthorized" );
simpleMappingExceptionResolver.setExceptionMappings(properties);
return simpleMappingExceptionResolver;
}
/** * 解决spring-boot Whitelabel Error Page * @return */
@Bean
public EmbeddedServletContainerCustomizer
containerCustomizer () {
return new EmbeddedServletContainerCustomizer() {
@Override
public void customize (ConfigurableEmbeddedServletContainer container) {
ErrorPage error401Page =
new ErrorPage(HttpStatus.UNAUTHORIZED,
"/unauthorized.html" );
ErrorPage error404Page =
new ErrorPage(HttpStatus.NOT_FOUND,
"/404.html" );
ErrorPage error500Page =
new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR,
"/500.html" );
container.addErrorPages(error401Page, error404Page, error500Page);
}
};
}
/** * cookie对象;会话Cookie模板 ,默认为: JSESSIONID 问题: 与SERVLET容器名冲突,从新定义为sid或rememberMe,自定义 * @return */
@Bean
public SimpleCookie
rememberMeCookie (){
SimpleCookie simpleCookie =
new SimpleCookie(
"rememberMe" );
simpleCookie.setHttpOnly(
true );
simpleCookie.setPath(
"/" );
simpleCookie.setMaxAge(
2592000 );
return simpleCookie;
}
/** * cookie管理对象;记住我功能,rememberMe管理器 * @return */
@Bean
public CookieRememberMeManager
rememberMeManager (){
CookieRememberMeManager cookieRememberMeManager =
new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
cookieRememberMeManager.setCipherKey(Base64.decode(
"4AvVhmFLUs0KTA3Kprsdag==" ));
return cookieRememberMeManager;
}
/** * FormAuthenticationFilter 过滤器 过滤记住我 * @return */
@Bean
public FormAuthenticationFilter
formAuthenticationFilter (){
FormAuthenticationFilter formAuthenticationFilter =
new FormAuthenticationFilter();
formAuthenticationFilter.setRememberMeParam(
"rememberMe" );
return formAuthenticationFilter;
}
/** * shiro缓存管理器; * 须要添加到securityManager中 * @return */
@Bean
public RedisCacheManager
cacheManager (){
RedisCacheManager redisCacheManager =
new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
redisCacheManager.setPrincipalIdFieldName(
"username" );
redisCacheManager.setExpire(
200000 );
return redisCacheManager;
}
/** * 让某个实例的某个方法的返回值注入为Bean的实例 * Spring静态注入 * @return */
@Bean
public MethodInvokingFactoryBean
getMethodInvokingFactoryBean (){
MethodInvokingFactoryBean factoryBean =
new MethodInvokingFactoryBean();
factoryBean.setStaticMethod(
"org.apache.shiro.SecurityUtils.setSecurityManager" );
factoryBean.setArguments(
new Object[]{securityManager()});
return factoryBean;
}
/** * 配置session监听 * @return */
@Bean (
"sessionListener" )
public ShiroSessionListener
sessionListener (){
ShiroSessionListener sessionListener =
new ShiroSessionListener();
return sessionListener;
}
/** * 配置会话ID生成器 * @return */
@Bean
public SessionIdGenerator
sessionIdGenerator () {
return new JavaUuidSessionIdGenerator();
}
@Bean
public RedisManager
redisManager (){
RedisManager redisManager =
new RedisManager();
return redisManager;
}
@Bean (
"sessionFactory" )
public ShiroSessionFactory
sessionFactory (){
ShiroSessionFactory sessionFactory =
new ShiroSessionFactory();
return sessionFactory;
}
/** * SessionDAO的做用是为Session提供CRUD并进行持久化的一个shiro组件 * MemorySessionDAO 直接在内存中进行会话维护 * EnterpriseCacheSessionDAO 提供了缓存功能的会话维护,默认状况下使用MapCache实现,内部使用ConcurrentHashMap保存缓存的会话。 * @return */
@Bean
public SessionDAO
sessionDAO () {
RedisSessionDAO redisSessionDAO =
new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
redisSessionDAO.setExpire(
12000 );
return redisSessionDAO;
}
/** * 配置保存sessionId的cookie * 注意:这里的cookie 不是上面的记住我 cookie 记住我须要一个cookie session管理 也须要本身的cookie * 默认为: JSESSIONID 问题: 与SERVLET容器名冲突,从新定义为sid * @return */
@Bean (
"sessionIdCookie" )
public SimpleCookie
sessionIdCookie (){
SimpleCookie simpleCookie =
new SimpleCookie(
"sid" );
simpleCookie.setHttpOnly(
true );
simpleCookie.setPath(
"/" );
simpleCookie.setMaxAge(-
1 );
return simpleCookie;
}
/** * 配置会话管理器,设定会话超时及保存 * @return */
@Bean (
"sessionManager" )
public SessionManager
sessionManager () {
ShiroSessionManager sessionManager =
new ShiroSessionManager();
Collection<SessionListener> listeners =
new ArrayList<SessionListener>();
listeners.add(sessionListener());
sessionManager.setSessionListeners(listeners);
sessionManager.setSessionIdCookie(sessionIdCookie());
sessionManager.setSessionDAO(sessionDAO());
sessionManager.setCacheManager(cacheManager());
sessionManager.setSessionFactory(sessionFactory());
sessionManager.setGlobalSessionTimeout(
1800000 );
sessionManager.setDeleteInvalidSessions(
true );
sessionManager.setSessionValidationSchedulerEnabled(
true );
sessionManager.setSessionValidationInterval(
3600000 );
sessionManager.setSessionIdUrlRewritingEnabled(
false );
return sessionManager;
}
/** * 并发登陆控制 * @return */
@Bean
public KickoutSessionControlFilter
kickoutSessionControlFilter (){
KickoutSessionControlFilter kickoutSessionControlFilter =
new KickoutSessionControlFilter();
kickoutSessionControlFilter.setSessionManager(sessionManager());
kickoutSessionControlFilter.setRedisManager(redisManager());
kickoutSessionControlFilter.setKickoutAfter(
false );
kickoutSessionControlFilter.setMaxSession(
1 );
kickoutSessionControlFilter.setKickoutUrl(
"/login?kickout=1" );
return kickoutSessionControlFilter;
}
/** * 配置密码比较器 * @return */
@Bean (
"credentialsMatcher" )
public RetryLimitHashedCredentialsMatcher
retryLimitHashedCredentialsMatcher (){
RetryLimitHashedCredentialsMatcher retryLimitHashedCredentialsMatcher =
new RetryLimitHashedCredentialsMatcher();
retryLimitHashedCredentialsMatcher.setRedisManager(redisManager());
return retryLimitHashedCredentialsMatcher;
}
}
ShiroRealm.java
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
package com.springboot.test.shiro.config.shiro;
import com.springboot.test.shiro.modules.user.dao.PermissionMapper;
import com.springboot.test.shiro.modules.user.dao.RoleMapper;
import com.springboot.test.shiro.modules.user.dao.entity.Permission;
import com.springboot.test.shiro.modules.user.dao.entity.Role;
import com.springboot.test.shiro.modules.user.dao.UserMapper;
import com.springboot.test.shiro.modules.user.dao.entity.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
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.beans.factory.annotation.Autowired;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/** * @author : wangsaichao * @date : 2018/5/10 * @description : 在Shiro中,最终是经过Realm来获取应用程序中的用户、角色及权限信息的 * 在Realm中会直接从咱们的数据源中获取Shiro须要的验证信息。能够说,Realm是专用于安全框架的DAO. */
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleMapper roleMapper;
@Autowired
private PermissionMapper permissionMapper;
/** * 验证用户身份 * @param authenticationToken * @return * @throws AuthenticationException */
@Override
protected AuthenticationInfo
doGetAuthenticationInfo (AuthenticationToken authenticationToken)
throws AuthenticationException {
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
String username = usernamePasswordToken.getUsername();
String password =
new String(usernamePasswordToken.getPassword());
User user =
this .userMapper.findByUserName(username);
if (user ==
null ) {
throw new UnknownAccountException(
"用户名或密码错误!" );
}
if (
"1" .equals(user.getState())) {
throw new LockedAccountException(
"帐号已被锁定,请联系管理员!" );
}
SimpleAuthenticationInfo info =
new SimpleAuthenticationInfo(user, user.getPassword(), getName());
return info;
}
/** * 受权用户权限 * 受权的方法是在碰到<shiro:hasPermission name=''></shiro:hasPermission>标签的时候调用的 * 它会去检测shiro框架中的权限(这里的permissions)是否包含有该标签的name值,若是有,里面的内容显示 * 若是没有,里面的内容不予显示(这就完成了对于权限的认证.) * * shiro的权限受权是经过继承AuthorizingRealm抽象类,重载doGetAuthorizationInfo(); * 当访问到页面的时候,连接配置了相应的权限或者shiro标签才会执行此方法不然不会执行 * 因此若是只是简单的身份认证没有权限的控制的话,那么这个方法能够不进行实现,直接返回null便可。 * * 在这个方法中主要是使用类:SimpleAuthorizationInfo 进行角色的添加和权限的添加。 * authorizationInfo.addRole(role.getRole()); authorizationInfo.addStringPermission(p.getPermission()); * * 固然也能够添加set集合:roles是从数据库查询的当前用户的角色,stringPermissions是从数据库查询的当前用户对应的权限 * authorizationInfo.setRoles(roles); authorizationInfo.setStringPermissions(stringPermissions); * * 就是说若是在shiro配置文件中添加了filterChainDefinitionMap.put("/add", "perms[权限添加]"); * 就说明访问/add这个连接必需要有“权限添加”这个权限才能够访问 * * 若是在shiro配置文件中添加了filterChainDefinitionMap.put("/add", "roles[100002],perms[权限添加]"); * 就说明访问/add这个连接必需要有 "权限添加" 这个权限和具备 "100002" 这个角色才能够访问 * @param principalCollection * @return */
@Override
protected AuthorizationInfo
doGetAuthorizationInfo (PrincipalCollection principalCollection) {
System.out.println(
"查询权限方法调用了!!!" );
User user = (User) SecurityUtils.getSubject().getPrincipal();
Set<Role> roles =
this .roleMapper.findRolesByUserId(user.getUid());
SimpleAuthorizationInfo authorizationInfo =
new SimpleAuthorizationInfo();
for (Role role : roles) {
authorizationInfo.addRole(role.getRole());
}
Set<Permission> permissions =
this .permissionMapper.findPermissionsByRoleId(roles);
for (Permission permission:permissions) {
authorizationInfo.addStringPermission(permission.getPermission());
}
return authorizationInfo;
}
/** * 重写方法,清除当前用户的的 受权缓存 * @param principals */
@Override
public void clearCachedAuthorizationInfo (PrincipalCollection principals) {
super .clearCachedAuthorizationInfo(principals);
}
/** * 重写方法,清除当前用户的 认证缓存 * @param principals */
@Override
public void clearCachedAuthenticationInfo (PrincipalCollection principals) {
super .clearCachedAuthenticationInfo(principals);
}
@Override
public void clearCache (PrincipalCollection principals) {
super .clearCache(principals);
}
/** * 自定义方法:清除全部 受权缓存 */
public void clearAllCachedAuthorizationInfo () {
getAuthorizationCache().clear();
}
/** * 自定义方法:清除全部 认证缓存 */
public void clearAllCachedAuthenticationInfo () {
getAuthenticationCache().clear();
}
/** * 自定义方法:清除全部的 认证缓存 和 受权缓存 */
public void clearAllCache () {
clearAllCachedAuthenticationInfo();
clearAllCachedAuthorizationInfo();
}
}
KickoutSessionControlFilter.java(限制并发登陆人数)
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
package com.springboot.test.shiro.config.shiro;
import java.io.Serializable;
import java.util.Deque;
import java.util.LinkedList;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import com.springboot.test.shiro.modules.user.dao.entity.User;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.resource.ResourceUrlProvider;
/** * @author : WangSaiChao * @date : 2018/5/23 * @description : shiro 自定义filter 实现 并发登陆控制 */
public class KickoutSessionControlFilter extends AccessControlFilter {
@Autowired
private ResourceUrlProvider resourceUrlProvider;
/** 踢出后到的地址 */
private String kickoutUrl;
/** 踢出以前登陆的/以后登陆的用户 默认踢出以前登陆的用户 */
private boolean kickoutAfter =
false ;
/** 同一个账号最大会话数 默认1 */
private int maxSession =
1 ;
private SessionManager sessionManager;
private RedisManager redisManager;
public static final String DEFAULT_KICKOUT_CACHE_KEY_PREFIX =
"shiro:cache:kickout:" ;
private String keyPrefix = DEFAULT_KICKOUT_CACHE_KEY_PREFIX;
public void setKickoutUrl (String kickoutUrl) {
this .kickoutUrl = kickoutUrl;
}
public void setKickoutAfter (
boolean kickoutAfter) {
this .kickoutAfter = kickoutAfter;
}
public void setMaxSession (
int maxSession) {
this .maxSession = maxSession;
}
public void setSessionManager (SessionManager sessionManager) {
this .sessionManager = sessionManager;
}
public void setRedisManager (RedisManager redisManager) {
this .redisManager = redisManager;
}
public String
getKeyPrefix () {
return keyPrefix;
}
public void setKeyPrefix (String keyPrefix) {
this .keyPrefix = keyPrefix;
}
private String
getRedisKickoutKey (String username) {
return this .keyPrefix + username;
}
/** * 是否容许访问,返回true表示容许 */
@Override
protected boolean isAccessAllowed (ServletRequest request, ServletResponse response, Object mappedValue)
throws Exception {
return false ;
}
/** * 表示访问拒绝时是否本身处理,若是返回true表示本身不处理且继续拦截器链执行,返回false表示本身已经处理了(好比重定向到另外一个页面)。 */
@Override
protected boolean onAccessDenied (ServletRequest request, ServletResponse response)
throws Exception {
Subject subject = getSubject(request, response);
if (!subject.isAuthenticated() && !subject.isRemembered()) {
return true ;
}
HttpServletRequest httpServletRequest = (HttpServletRequest)request;
String path = httpServletRequest.getServletPath();
if (isStaticFile(path)){
return true ;
}
Session session = subject.getSession();
String username = ((User) subject.getPrincipal()).getUsername();
Serializable sessionId = session.getId();
Deque<Serializable> deque = (Deque<Serializable>) redisManager.get(getRedisKickoutKey(username));
if (deque ==
null || deque.size()==
0 ) {
deque =
new LinkedList<Serializable>();
}
if (!deque.contains(sessionId) && session.getAttribute(
"kickout" ) ==
null ) {
deque.push(sessionId);
}
while (deque.size() > maxSession) {
Serializable kickoutSessionId =
null ;
if (kickoutAfter) {
kickoutSessionId=deque.getFirst();
kickoutSessionId = deque.removeFirst();
}
else {
kickoutSessionId = deque.removeLast();
}
try {
Session kickoutSession = sessionManager.getSession(
new DefaultSessionKey(kickoutSessionId));
if (kickoutSession !=
null ) {
kickoutSession.setAttribute(
"kickout" ,
true );
}
}
catch (Exception e) {
e.printStackTrace();
}
}
redisManager.set(getRedisKickoutKey(username), deque);
if (session.getAttribute(
"kickout" ) !=
null ) {
try {
subject.logout();
}
catch (Exception e) {
}
WebUtils.issueRedirect(request, response, kickoutUrl);
return false ;
}
return true ;
}
private boolean isStaticFile (String path) {
String staticUri = resourceUrlProvider.getForLookupPath(path);
return staticUri !=
null ;
}
}
RetryLimitHashedCredentialsMatcher.java(登陆错误次数限制)
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
package com.springboot.test.shiro.config.shiro;
import java.util.concurrent.atomic.AtomicInteger;
import com.springboot.test.shiro.modules.user.dao.UserMapper;
import com.springboot.test.shiro.modules.user.dao.entity.User;
import org.apache.log4j.Logger;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.springframework.beans.factory.annotation.Autowired;
/** * @author : WangSaiChao * @date : 2018/5/25 * @description : 登录次数限制 */
public class RetryLimitHashedCredentialsMatcher extends SimpleCredentialsMatcher {
private static final Logger logger = Logger.getLogger(RetryLimitHashedCredentialsMatcher.class);
public static final String DEFAULT_RETRYLIMIT_CACHE_KEY_PREFIX =
"shiro:cache:retrylimit:" ;
private String keyPrefix = DEFAULT_RETRYLIMIT_CACHE_KEY_PREFIX;
@Autowired
private UserMapper userMapper;
private RedisManager redisManager;
public void setRedisManager (RedisManager redisManager) {
this .redisManager = redisManager;
}
private String
getRedisKickoutKey (String username) {
return this .keyPrefix + username;
}
@Override
public boolean doCredentialsMatch (AuthenticationToken token, AuthenticationInfo info) {
String username = (String)token.getPrincipal();
AtomicInteger retryCount = (AtomicInteger)redisManager.get(getRedisKickoutKey(username));
if (retryCount ==
null ) {
retryCount =
new AtomicInteger(
0 );
}
if (retryCount.incrementAndGet() >
5 ) {
User user = userMapper.findByUserName(username);
if (user !=
null &&
"0" .equals(user.getState())){
user.setState(
"1" );
userMapper.update(user);
}
logger.info(
"锁定用户" + user.getUsername());
throw new LockedAccountException();
}
boolean matches =
super .doCredentialsMatch(token, info);
if (matches) {
redisManager.del(getRedisKickoutKey(username));
}{
redisManager.set(getRedisKickoutKey(username), retryCount);
}
return matches;
}
/** * 根据用户名 解锁用户 * @param username * @return */
public void unlockAccount (String username){
User user = userMapper.findByUserName(username);
if (user !=
null ){
user.setState(
"0" );
userMapper.update(user);
redisManager.del(getRedisKickoutKey(username));
}
}
}
ShiroSessionListener.java(session监听)
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
package com.springboot.test.shiro.config.shiro;
import com.springboot.test.shiro.Application;
import com.springboot.test.shiro.modules.user.dao.entity.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionListener;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/** * @author : wangsaichao * @date : 2018/5/15 * @description : 配置session监听器 , */
public class ShiroSessionListener implements SessionListener {
/** * 统计在线人数 * juc包下线程安全自增 */
private final AtomicInteger sessionCount =
new AtomicInteger(
0 );
/** * 会话建立时触发 * @param session */
@Override
public void onStart (Session session) {
sessionCount.incrementAndGet();
}
/** * 退出会话时触发 * @param session */
@Override
public void onStop (Session session) {
sessionCount.decrementAndGet();
}
/** * 会话过时时触发 * @param session */
@Override
public void onExpiration (Session session) {
sessionCount.decrementAndGet();
}
/** * 获取在线人数使用 * @return */
public AtomicInteger
getSessionCount () {
return sessionCount;
}
}
上面的类中有一些依赖类,并无贴出来,该些类是为了解决Shiro整合Redis 频繁获取或更新 Session 将在下一篇博客中讲,依赖的一些类,也在下篇博客中贴出来。点击进入下一篇博客:git