分布式Shiro,SpringBoot項目Shiro整合Redis
====================重要 Begin====================
你的SpringBoot項目已經使用了Shiro,并且可以正常使用。本篇文章的主要目的是將Shiro保存在服務器內存中的session信息改為使用Redis保存session信息
====================重要 End====================
正文開始
0、前情概要
由于shiro不支持分布式場景下使用(可能是支持,但沒找到),但是現在項目都是分布式的項目,一個服務要部署多個實例,明顯shiro已經無法滿足現有的情況。為了使shiro可以在分布式項目中使用,博主查閱了很多資料,其中一個包括引入shiro-redis
依賴來實現(并未實操),但是看這個依賴最新的版本還是在2020年,果斷放棄(應該有很多漏洞,我司對漏洞的把控極為嚴格!!!),所以就想著是否可以根據目前掌握的技術和網上的實踐來手動實現一下shiro整合redis。以下就是博主的實現過程(碼多話少,有事滴滴~)
1、引入依賴
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>
</dependency>
2、創建Redis配置類
/*** Redis配置類*/
@EnableCaching
@Configuration
public class RedisConfiguration {@AutowiredRedisCacheProperties redisCacheProperties;@Bean()public RedisTemplate<String, Object> redisShiroTemplate(@Autowired RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);template.setKeySerializer(new StringRedisSerializer());template.setHashKeySerializer(new StringRedisSerializer());// shiro序列化存儲session存在問題(https://www.cnblogs.com/ReturnOfTheKing/p/18224205)/*template.setValueSerializer(new GenericJackson2JsonRedisSerializer());template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());*/return template;}@Bean(name = {"cacheKeyGenerator"})public KeyGenerator cacheKeyGenerator() {return (Object o, Method method, Object... objects) -> {StringBuilder sb = new StringBuilder();sb.append(o.getClass().getName());sb.append(method.getName());for (Object obj : objects) {sb.append(obj.toString());}return sb.toString();};}@Bean(name = "cacheManager")public RedisCacheManager cacheManager(@Autowired RedisConnectionFactory redisConnectionFactory) {RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofDays(7)).serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())).disableCachingNullValues();RedisCacheManager.RedisCacheManagerBuilder builder =RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory);Set<String> cacheNames = new HashSet<>();ConcurrentHashMap<String, RedisCacheConfiguration> cacheConfig = new ConcurrentHashMap<>();for (Map.Entry<String, Duration> entry : redisCacheProperties.getCacheDuration().entrySet()) {cacheNames.add(entry.getKey());cacheConfig.put(entry.getKey(), defaultConfig.entryTtl(entry.getValue()));}RedisCacheManager cacheManager = builder.transactionAware().cacheDefaults(defaultConfig).initialCacheNames(cacheNames).withInitialCacheConfigurations(cacheConfig).build();return cacheManager;}
}
/*** RedisCache參數*/
@Component
@Getter
public class RedisCacheProperties {private final Map<String, Duration> cacheDuration = new HashMap<>();
}
3、繼承AbstractSessionDAO,創建自定義RedisSessionDao 類
/*** 自定義RedisSessionDAO*/
@Component
public class RedisSessionDao extends AbstractSessionDAO {@Value("${session.redis.expireTime}")private long expireTime;@Autowiredprivate RedisTemplate<String, Object> redisShiroTemplate;private String getKey(String originalKey) {return "shiro_redis_session_key_:" + originalKey;}@Overrideprotected Serializable doCreate(Session session) {Serializable sessionId = this.generateSessionId(session);this.assignSessionId(session, sessionId);redisShiroTemplate.opsForValue().set(getKey(session.getId().toString()), session, expireTime, TimeUnit.SECONDS);return sessionId;}@Overrideprotected Session doReadSession(Serializable sessionId) {return sessionId == null ? null : (Session) redisShiroTemplate.opsForValue().get(getKey(sessionId.toString()));}@Overridepublic void update(Session session) throws UnknownSessionException {if (session != null && session.getId() != null) {session.setTimeout(expireTime * 1000);redisShiroTemplate.opsForValue().set(getKey(session.getId().toString()), session, expireTime, TimeUnit.SECONDS);}}@Overridepublic void delete(Session session) {if (session != null && session.getId() != null) {redisShiroTemplate.opsForValue().getOperations().delete(getKey(session.getId().toString()));}}@Overridepublic Collection<Session> getActiveSessions() {return Collections.emptySet();}
}
4、在ShiroConfiguration配置類中使用自定義SessionDAO
@Bean
public SessionManager shiroSessionManager() {DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();//session過期時間sessionManager.setGlobalSessionTimeout(expireTime * 1000);sessionManager.setSessionDAO(redisSessionDao);return sessionManager;
}
問題
往redis中放的數據記得實現 序列化接口
,不然會報錯!
參考
- 分布式shiro,session共享
- Shiro權限管理框架(二):Shiro結合Redis實現分布式環境下的Session共享
- shiro org.apache.shiro.session.mgt.SimpleSession對象 反序列化失敗