Shiro使用redis作為快取(解決shiro頻繁訪問Redis)(十一)
阿新 • • 發佈:2019-01-30
之前寫過一篇部落格,使用的一個開源專案,實現了redis作為快取 快取使用者的許可權 和 session資訊,還有兩個功能沒有修改,一個是使用者併發登入限制,一個是使用者密碼錯誤次數.本篇中幾個類 也是使用的開源專案中的類,只不過是拿出來了,redis單獨做的配置,方便進行優化。
整合過程
1.首先是整合Redis
Redis客戶端使用的是RedisTemplate,自己寫了一個序列化工具繼承RedisSerializer
SerializeUtils.java
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
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);
//逐出連線的最小空閒時間 預設1800000毫秒(30分鐘)
jedisPoolConfig.setMinEvictableIdleTimeMillis(300000);
//每次逐出檢查時 逐出的最大數目 如果為負數就是 : 1/abs(n), 預設3
jedisPoolConfig.setNumTestsPerEvictionRun(10);
//逐出掃描的時間間隔(毫秒) 如果為負數,則不執行逐出執行緒, 預設-1
jedisPoolConfig.setTimeBetweenEvictionRunsMillis(30000);
//是否在從池中取出連線前進行檢驗,如果檢驗失敗,則從池中去除連線並嘗試取出另一個
jedisPoolConfig.setTestOnBorrow(true);
//在空閒時檢查有效性, 預設false
jedisPoolConfig.setTestWhileIdle(true);
return jedisPoolConfig;
}
/**
* 配置工廠
* @param jedisPoolConfig
* @return
*/
@Bean
public JedisConnectionFactory jedisConnectionFactory(JedisPoolConfig jedisPoolConfig){
JedisConnectionFactory jedisConnectionFactory=new JedisConnectionFactory();
//連線池
jedisConnectionFactory.setPoolConfig(jedisPoolConfig);
//IP地址
jedisConnectionFactory.setHostName(host);
//埠號
jedisConnectionFactory.setPort(port);
//如果Redis設定有密碼
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());
//開啟事務
//stringRedisTemplate.setEnableTransactionSupport(true);
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
}
RedisManager.java
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;
//=============================common============================
/**
* 指定快取失效時間
* @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);
}
//============================String=============================
/**
* 普通快取獲取
* @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 = 0L;
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
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 = 0L;
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獲得的。
讀取使用者許可權資訊時,還用到兩個異常類,如下:
PrincipalInstanceException.java
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
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
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
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 = 1000L;
/**
* 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) {
// 如果沒有主要欄位(除lastAccessTime以外其他欄位)發生改變
ShiroSession ss = (ShiroSession) session;
if (!ss.isChanged()) {
return;
}
//如果沒有返回 證明有呼叫 setAttribute往redis 放的時候永遠設定為false
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 = 0L;
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
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;
impor