分布式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对象 反序列化失败