前述の
この記事では、shiroが単一のアプリケーションで使用する必要のあるコードを変更して、shiroがRedisをセッション管理として使用して分散セッション機能を実現するようにします。
クラスター環境では、キャッシュとセッションを共有できるようにクラスター内に複数のサーバーが必要です。現在一般的なソリューションは、Redisデータベースをキャッシュサーバーとして使用することです。Shiroは、キャッシュのためのRedisの統合サポートを公式に提供していません。公式のサードパーティ拡張ライブラリであるShiro-RedisにRedisのサポートがあります。
ただし、このサードパーティの拡張機能が最近更新されてから2年が経過しました。多くの依存関係が低すぎて、アップグレードされていません。次のアドレスはfork
、変更された私のコピーです。現在のバージョンはアップグレードされていshiro
ますjedis
。依存関係がアップグレードされました。後で調べます。fastjson2
シリアル化として使用する方法。shiro-redis v3.7
導入方法はほぼ同じですが、その後、shiroのキャッシュとしてRedisの使用を実装し始めます。
1.POMの依存関係を変更します
1.最初に、、、およびのコンテンツを含む、プロジェクト内のすべての 関連 する依存shiro-ehcache
関係とコードを 削除しますpom.xml
ehcache.xml
ShiroConfig
shiro-redis
2.依存関係を導入します
<!-- shiro整合redis:这里直接引用了博主自己升级了有关依赖得jar包 -->
<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.Yamlが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シリアル化構成
ここで使用されるのはFastJson2
/**
* @className: FastJson2JsonRedisSerializer
* @description: Redis使用FastJson序列化
*/
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);
}
}
/**
* @className: RedisConfig
* @description: redis序列化配置
*/
@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;
}
}
第四に、shiroの構成を変更します
@Configuration
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());
// ......
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("xxxxxx"));
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;
}
}
テスト
起動してシステムにログインした後、redisコンソールのキャッシュステータスを確認します