原文地址,轉載請註明出處: 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