Shiro分布式缓存和集群会话
前述🚨
以《在单体应用中使用》中的代码基础上更新,shiro使用Redis作为会话缓存层,从而实现项目负载均衡等功能。
在集群环境中,我们需要集群中的多台服务器能够共享缓存和会话,目前流行的方案是使用Redis数据库来作为缓存服务器。Shiro 官方没有提供对 Redis 做缓存的集成支持,在官方提供的第三方扩展库中有对 Redis的支持。
下面时博主自己fork
的一份进行了修改,目前是升级了shiro等一些依赖,后续在研究怎么使用fastjson2
作为序列化。
实现过程🔎
1、修改POM依赖
1.1、去除项目中所有 shiro-ehcache
依赖,包括 pom.xml
、ehcache.xml
、ShiroConfig
中有关内容
1.2、引入shiro-redis
依赖
这里直接引用了博主自己升级了有关依赖得jar包
<!-- shiro整合redis -->
<dependency>
<groupId>io.github.LiJunYi2</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.7</version>
</dependency>
<!-- springboot整合redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.9</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2-extension</artifactId>
<version>2.0.9</version>
</dependency>
2、Yml中新增Redis有关配置
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
password:
timeout: 6000ms
lettuce:
pool:
max-active: 1000
max-wait: -1ms
max-idle: 10
min-idle: 5
3、Redis序列化配置
/**
* @version 1.0.0
* @className: FastJson2JsonRedisSerializer
* @description: Redis使用FastJson序列化
* @author: LiJunYi
* @create: 2022-02-23
*/
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
{
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
private Class<T> clazz;
public FastJson2JsonRedisSerializer(Class<T> clazz)
{
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException
{
if (t == null)
{
return new byte[0];
}
return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException
{
if (bytes == null || bytes.length <= 0)
{
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
}
}
/**
* @version 1.0.0
* @className: RedisConfig
* @description: redis序列化配置
* @author: LiJunYi
* @create: 2022-02-23
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
{
@Bean
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
{
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
// Hash的key也采用StringRedisSerializer的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
4、Shiro配置类修改
/**
* 功能描述: shiro配置
* @author: LiJunYi
* @date: 2022-02-23
* @version: v1.0.0
*/
public class ShiroConfig {
/**
* redis缓存地址
*/
@Value("${spring.redis.port}")
private String redisPort;
/**
* redis缓存端口
*/
@Value("${spring.redis.host}")
private String redisHost;
/**
* redis数据库索引
*/
@Value("${spring.redis.database}")
private int database;
/**
* redis密码
*/
@Value("${spring.redis.password}")
private String password;
/**
* 登录网址
*/
@Value("${shiro.user.loginUrl}")
private String loginUrl;
/**
* 成功的url
*/
@Value("${shiro.user.successUrl}")
private String successUrl;
/**
* 未经授权的url
*/
@Value("${shiro.user.unauthorizedUrl}")
private String unauthorizedUrl;
/**
* Cache Manager (shiro-redis)
*/
@Bean
public RedisCacheManager redisCacheManager()
{
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
redisCacheManager.setPrincipalIdFieldName("loginName");
redisCacheManager.setValueSerializer(new FstSerializer());
return redisCacheManager;
}
/**
* RedisManager (shiro-redis)
*/
@Bean
public IRedisManager redisManager()
{
LettuceRedisManager redisManager = new LettuceRedisManager(redisHost, Convert.toInt(redisPort));
redisManager.setDatabase(database);
redisManager.setTimeout(30 * 60);
return redisManager;
}
/**
* 自定义Realm
*/
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm shiroRealm = new MyShiroRealm();
shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
shiroRealm.setCacheManager(redisCacheManager());
return shiroRealm;
}
/**
* 凭证匹配器 (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
* 所以我们需要修改下doGetAuthenticationInfo中的代码; )
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashAlgorithmName("md5");
hashedCredentialsMatcher.setHashIterations(1024);
return hashedCredentialsMatcher;
}
/**
* 退出过滤器
*/
public LogoutFilter logoutFilter(){
LogoutFilter logoutFilter = new LogoutFilter();
logoutFilter.setLoginUrl(loginUrl);
logoutFilter.setCacheManager(redisCacheManager());
return logoutFilter;
}
/**
* RedisSessionDAO (shiro-redis)
*/
@Bean
public RedisSessionDAO redisSessionDAO()
{
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
// custom session key prefix
//redisSessionDAO.setKeyPrefix("");
// custom session value serializer, default is jdk serializer.
redisSessionDAO.setValueSerializer(new FstSerializer());
redisSessionDAO.setExpire(30 * 60);
return redisSessionDAO;
}
/**
* 会话管理器
*/
@Bean
public SessionManager sessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
// 加入缓存管理器
sessionManager.setCacheManager(redisCacheManager());
// 去掉JSESSIONID
sessionManager.setSessionIdUrlRewritingEnabled(false);
sessionManager.setSessionIdCookie(simpleCookie());
// 自定义SessionDao
sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}
/**
* 安全管理器 securityManager
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
// 注入缓存管理器
securityManager.setCacheManager(redisCacheManager());
//注入记住我管理器
securityManager.setRememberMeManager(rememberMeManager());
// 注入session管理
securityManager.setSessionManager(sessionManager());
return securityManager;
}
/**
* Shiro过滤器配置
* */
@Bean
public ShiroFilterFactoryBean shiroFilter(){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager());
// 设置login URL
shiroFilterFactoryBean.setLoginUrl(loginUrl);
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl(successUrl);
// 未授权的页面
shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);
// 拦截器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 部分静态资源(首页部分静态资源开放)
filterChainDefinitionMap.put("/static/ajax/system/login/**", "anon");
filterChainDefinitionMap.put("/static/ajax/lib/gVerify.js", "anon");
filterChainDefinitionMap.put("/static/layuimini/**", "anon");
// 登录
filterChainDefinitionMap.put("/login","anon");
filterChainDefinitionMap.put("/index","anon");
// 菜单
filterChainDefinitionMap.put("/index/menu","anon");
/* 退出系统*/
filterChainDefinitionMap.put("/logout","logout");
// 其他需要鉴权
filterChainDefinitionMap.put("/**","kickout,authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
Map<String, Filter> filters = new LinkedHashMap<>(8);
// 注销成功,则跳转到指定页面
filters.put("logout", logoutFilter());
// 限制登录
filters.put("kickout",kickoutSessionControlFilter());
shiroFilterFactoryBean.setFilters(filters);
return shiroFilterFactoryBean;
}
private SimpleCookie simpleCookie()
{
SimpleCookie simpleCookie = new SimpleCookie("shiro.sesssion");
simpleCookie.setPath("/");
return simpleCookie;
}
/**
* cookie 属性设置
*/
private SimpleCookie rememberMeCookie(){
//这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
//如果httyOnly设置为true,则客户端不会暴露给客户端脚本代码
simpleCookie.setHttpOnly(true);
simpleCookie.setMaxAge(-1);
simpleCookie.setPath("/");
return simpleCookie;
}
/** rememberMeManager管理器
* rememberMeManager()方法是生成rememberMe管理器,而且要将这个rememberMe管理器设置到securityManager中
*/
private CookieRememberMeManager rememberMeManager(){
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCipherKey(Base64.decode("slgp8DcK+R3pFTO/ZJj5Tw=="));
cookieRememberMeManager.setCookie(rememberMeCookie());
return cookieRememberMeManager;
}
/**
* 会话调度器
*/
@Bean
public ExecutorServiceSessionValidationScheduler scheduler(){
ExecutorServiceSessionValidationScheduler scheduler = new ExecutorServiceSessionValidationScheduler();
scheduler.setInterval(30 * 60 * 1000);
return scheduler;
}
/**
* 同一个用户多设备登录限制
*/
public KickoutSessionControlFilter kickoutSessionControlFilter(){
KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter();
kickoutSessionControlFilter.setCacheManager(redisCacheManager());
kickoutSessionControlFilter.setSessionManager(sessionManager());
// 同一个用户最大的会话数,默认-1无限制;比如2的意思是同一个用户允许最多同时两个人登录
kickoutSessionControlFilter.setMaxSession(1);
// 是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;踢出顺序
kickoutSessionControlFilter.setKickoutAfter(false);
kickoutSessionControlFilter.setKickoutUrl("/login?kickout=1");
return kickoutSessionControlFilter;
}
/**
* 在thymeleaf 使用shiro页面标签
* */
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
/**
* 开启Shiro注解通知器
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
/**
* DefaultAdvisorAutoProxyCreator,Spring的一个bean,由Advisor决定对哪些类的方法进行AOP代理。
*/
@Bean
@ConditionalOnMissingBean
public static DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultApp = new DefaultAdvisorAutoProxyCreator();
defaultApp.setProxyTargetClass(true);
return defaultApp;
}
}