Shiro的Session缓存主要有两种方案,一种是使用Shiro自己的Session,不使用HttpSession,自己实现Shiro的Cache接口和Session缓存等;另外一种是直接使用spring boot的spring-session-data-redis
的包,并且配置RedisTemplate和Redis的序列化方法就可以了。相对来说第二种方式非常简单,第一种还需要不少开发工作。下面主要来说第一种方式的思路。
Shiro的缓存
要缓存Session,最好要先集成Shiro的缓存。Shiro提供了类似于Spring的Cache抽象,即Shiro本身不实现Cache,但是对Cache进行了又抽象,方便更换不同的底层Cache实现。 Shiro提供了Cache接口和CacheManager接口,以及CacheManagerAware接口来注入CacheManager。
- 实现Cache的缓存接口,shiro的Cache对进行Spring Cache包装
@SuppressWarnings("unchecked")
class SpringCacheWrapper <V> implements Cache<String, V> {
private final String REDIS_SHIRO_CACHE = "shiro-cache#";
private org.springframework.cache.Cache springCache;
private CacheManager cacheManager;
SpringCacheWrapper(CacheManager cacheManager, @NotNull org.springframework.cache.Cache springCache) {
this.springCache = springCache;
this.cacheManager = cacheManager;
}
@Override
public V get(String key) throws CacheException {
ValueWrapper cacheValue = springCache.get(getCacheKey(key));
return (V) Optional.ofNullable(cacheValue).map(p->p.get()).orElse(null);
}
@Override
public V put(String key, V value) throws CacheException {
springCache.put(getCacheKey(key), value);
return value;
}
@Override
public V remove(String key) throws CacheException {
springCache.evict(getCacheKey(key));
return null;
}
private String getCacheKey(String key) {
return REDIS_SHIRO_CACHE + key;
}
@Override
public void clear() throws CacheException {
springCache.clear();
}
@Override
public int size() {
throw new UnsupportedOperationException("invoke spring cache size method not supported");
}
@Override
public Set<String> () {
throw new UnsupportedOperationException("invoke spring cache keys method not supported");
}
@Override
public Collection<V> values() {
throw new UnsupportedOperationException("invoke spring cache values method not supported");
}
}
实现CacheManager的缓存接口,shiro的CacheManager对进行Spring CacheManager的包装
public class SpringCacheManagerWrapper implements CacheManager {
private org.springframework.cache.CacheManager cacheManager;
public SpringCacheManagerWrapper(org.springframework.cache.CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
@Override
public <K, V> Cache<K, V> getCache(String name) throws CacheException {
org.springframework.cache.Cache springCache = cacheManager.getCache(name);
return new SpringCacheWrapper(springCache);
}
}
Session的缓存
需要实现CacheSessionDAO接口,实现Session的缓存方法。
public class ShiroSessionDAO extends CachingSessionDAO {
private Cache<String, Session> cache;
public ShiroSessionDAO(CacheManager cacheManager) {
String cacheName = getActiveSessionsCacheName();
this.setCacheManager(cacheManager);
this.cache = getCacheManager().getCache(cacheName);
}
@Override
protected void doUpdate(Session session) {
if(session==null) {
return;
}
cache.put(session.getId().toString(), session);
}
@Override
protected void doDelete(Session session) {
if(session==null){
return;
}
cache.remove(session.getId().toString());
}
@Override
protected Serializable doCreate(Session session) {
if(session == null) {
return null;
}
Serializable sessionId = this.generateSessionId(session);
assignSessionId(session, sessionId);
cache.put(sessionId.toString(), session);
return sessionId;
}
@Override
protected Session doReadSession(Serializable sessionId) {
if(sessionId==null) {
return null;
}
Session session=(Session) cache.get(sessionId.toString());
return session;
}
}
配置Bean
配置Redis
@Configuration
public class RedisConfiguration extends CachingConfigurerSupport {
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append("#" + method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Bean
public RedisCacheManager redisCacheManager(@Autowired RedisTemplate redisTemplate) {
// spring cache注解序列化配置
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getKeySerializer()))
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()))
.disableCachingNullValues()
.entryTtl(Duration.ofSeconds(60));
Set<String> cacheNames = new HashSet<>();
cacheNames.add("user");
Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
configMap.put("user", redisCacheConfiguration.entryTtl(Duration.ofSeconds(120)));
RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisTemplate.getConnectionFactory())
.cacheDefaults(redisCacheConfiguration).transactionAware().initialCacheNames(cacheNames)
.withInitialCacheConfigurations(configMap).build();
return redisCacheManager;
}
@Bean
public RedisTemplate<Object, Object> redisTemplate(@Autowired RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<Object, Object>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
StringRedisSerializer stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer); // key序列化
redisTemplate.setHashKeySerializer(stringSerializer); // Hash key序列化
FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<Object>(Object.class);
redisTemplate.setValueSerializer(fastJsonRedisSerializer); // value序列化
redisTemplate.setHashValueSerializer(fastJsonRedisSerializer); // Hash value序列化
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
配置Shiro的缓存和CacheSessionDao
@Bean(name = "securityManager")
public org.apache.shiro.mgt.SecurityManager defaultWebSecurityManager(@Autowired UserRealm userRealm, @Autowired TokenRealm tokenValidateRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setAuthenticator(multiRealmAuthenticator());
securityManager.setRealms(Arrays.asList(userRealm, tokenValidateRealm));
securityManager.setRememberMeManager(rememberMeManager());
securityManager.setCacheManager(new SpringCacheManagerWrapper());
//必须使用DefaultWebSessionManager,不能是ServletContainerSessionManager
DefaultWebSessionManager webSessionManager = new DefaultWebSessionManager();
webSessionManager.setSessionDAO(cachingSessionDAO);
securityManager.setSessionManager(webSessionManager);
return securityManager;
}
总结
从上面的步骤可以看出配置Shiro的Session缓存,还是比较麻烦的。本来也是打算采用这种方式,后来在网上发现有个Session集成Redis的包,如下所示,也发现使用这种方式更简单,后来就直接使用Spring的Session了,只需要配置Redis(如上所示)就可以了。
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>