整合redis作为缓存和会话管理
两种方式
先说比较难的
1.自己实现
导入jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
application.yml 修改
redis:
port: 6379
host: localhost
password: 123456
database: 3
shiro有默认的的缓存实现(Ehcache和内存),也可以实现 接口替换成其他的实现
要实现的接口 Cache, CacheManger
代码
package com.han.springbootshiro.config.redis;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Set;
@Component
public class RedisCache<k,v> implements Cache<k,v> {
private String cacheName;
@Autowired
private RedisTemplate redisTemplate;
public void setCacheName(String cacheName) {
this.cacheName = cacheName;
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
}
public RedisCache(){
}
@Override
public v get(k k) throws CacheException {
System.out.println("put key:" + k);
//return (v) getRedisTemplate().opsForValue().get(k.toString());
return (v) redisTemplate.opsForHash().get(this.cacheName, k.toString());
}
@Override
public v put(k k, v v) throws CacheException {
System.out.println("put key:" + k);
System.out.println("put value:" + v);
//getRedisTemplate().opsForValue().set(k.toString(),v);
redisTemplate.opsForHash().put(this.cacheName, k.toString(), v);
return null;
}
@Override
public v remove(k k) throws CacheException {
return (v)redisTemplate.opsForHash().delete(this.cacheName, k.toString());
}
@Override
public void clear() throws CacheException {
redisTemplate.delete(this.cacheName);
}
@Override
public int size() {
return redisTemplate.opsForHash().size(this.cacheName).intValue();
}
@Override
public Set<k> keys() {
return redisTemplate.opsForHash().keys(this.cacheName);
}
@Override
public Collection<v> values() {
return redisTemplate.opsForHash().values(this.cacheName);
}
}
package com.han.springbootshiro.config.redis;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
@Component
public class RedisCacheManger implements CacheManager {
@Autowired
private RedisCache redisCache;
@Override
public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {
redisCache.setCacheName(cacheName);
return redisCache;
}
}
修改ShiroConfig.java
package com.han.springbootshiro.config.shiro;
import com.han.springbootshiro.config.redis.RedisCacheManger;
import org.apache.shiro.mgt.SessionsSecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ShiroConfig {
@Autowired
private RedisCacheManger redisCacheManger;
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(getSessionsSecurityManager());
return shiroFilterFactoryBean;
}
@Bean
public SessionsSecurityManager getSessionsSecurityManager(){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(getRealm());
return defaultWebSecurityManager;
}
@Bean
public Realm getRealm(){
ShiroRealm shiroRealm = new ShiroRealm();
//开启redis缓存管理
shiroRealm.setCacheManager(redisCacheManger);
shiroRealm.setCachingEnabled(true); //开启全局缓存
shiroRealm.setAuthenticationCachingEnabled(true); //开启认证缓存
shiroRealm.setAuthenticationCacheName("authenticationCache");
shiroRealm.setAuthorizationCachingEnabled(true); //开启授权缓存
shiroRealm.setAuthorizationCacheName("authorizationCache");
return shiroRealm;
}
}
以上是使用shiro作为登录和认证缓存,下边是使用redis作会话管理,也是自己实现接口,然后替换掉默认的实现类,就是将自定义的会话管理器注册到安全管理器(SessionsSecurityManager)
redis做会话管理
继承AbstractSessionDAO,重写session的增删改查方法
package com.han.springbootshiro.config.shiro;
import com.alibaba.fastjson2.JSON;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
@Component
public class SelfSessionDao extends AbstractSessionDAO {
@Autowired
private RedisTemplate<Object,Object> redisTemplate;
private final String prefix = "shiro:session";
// 创建session,保存到数据库
@Override
protected Serializable doCreate(Session session) {
Serializable sessionId = generateSessionId(session);
String s = JSON.toJSONString(session);
System.out.println("doCreate:" + s);
// 必须要将生成的id设置到session实力当中
assignSessionId(session,sessionId);
redisTemplate.boundValueOps(prefix + sessionId).set(session);
return sessionId;
}
@Override
protected Session doReadSession(Serializable sessionId) {
return (Session) redisTemplate.boundValueOps(prefix + sessionId).get();
}
@Override
public void update(Session session) throws UnknownSessionException {
if (session == null || session.getId() == null) {
throw new UnknownSessionException("session 或者 session为空");
}
redisTemplate.boundValueOps(prefix + session.getId()).set(session);
}
@Override
public void delete(Session session) {
redisTemplate.delete(prefix + session.getId());
}
@Override
public Collection<Session> getActiveSessions() {
Set<Object> keys = redisTemplate.keys(prefix);
if (keys != null) {
return keys.stream().map(key -> {
Session s = (Session) redisTemplate.boundValueOps(key).get();
return s;
}).collect(Collectors.toList());
} else {
return null;
}
}
}
定义自己的sessionManger,定义从请求头中获取token
package com.han.springbootshiro.config.shiro;
import com.alibaba.druid.util.StringUtils;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
public class CustomSessionManager extends DefaultWebSessionManager {
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
String id = WebUtils.toHttp(request).getHeader("Access-Token");
if (StringUtils.isEmpty(id)) {
// 获取sessionId,id可以自定义
return super.getSessionId(request, response);
} else {
//返回sessionId;固定套路
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header");
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
}
}
}
session生成逻辑
package com.han.springbootshiro.config.shiro;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.eis.SessionIdGenerator;
import java.io.Serializable;
import java.util.UUID;
public class CustomSessionIdGenerator implements SessionIdGenerator {
@Override
public Serializable generateId(Session session) {
//... 生成id逻辑
return UUID.randomUUID().toString();
}
}
//将自定义的会话管理器注册到安全管理器中
/**
* 会话管理器
* @param
* @return
*/
@Bean
public DefaultWebSessionManager sessionManager() {
CustomSessionManager sessionManager = new CustomSessionManager();
// 自定义sessionDAO
sessionManager.setSessionDAO(selfSessionDao);
// 自定义id生成器
selfSessionDao.setSessionIdGenerator(new CustomSessionIdGenerator());
return sessionManager;
}
@Bean
public SessionsSecurityManager getSessionsSecurityManager(){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(getRealm());
//将自定义的redis缓存管理器注册到安全管理器中
defaultWebSecurityManager.setCacheManager(redisCacheManger);
//将自定义的会话管理器注册到安全管理器中
defaultWebSecurityManager.setSessionManager(sessionManager());
return defaultWebSecurityManager;
}
一个BUG
有一次偶然发现,在退出登录的时候,在redis缓存中,session清空了,但是用户的信息还在,很不理解,后来看了一下自己实现的rediscache remove 方法,打印了一下key
key竟然是个对象结构是
User{id='1',username:'12',....}
后来想会不会是我在用户身份认证时subject的principal存储的整个对象导致的
但是rediscache 的其他方法的key都是 username,只有这个remove是User对象
解决方法 ShiroRealm重写 获取CacheKey方法
@Override
protected Object getAuthorizationCacheKey(PrincipalCollection principals) {
User user = (User) principals.getPrimaryPrincipal();
return user.getUsername();
}
@SuppressWarnings("unchecked")
@Override
protected Object getAuthenticationCacheKey(PrincipalCollection principals) {
User user = (User) principals.getPrimaryPrincipal();
return user.getUsername();
}
后来想了想,用户登录时login方法是从UsernamePasswordToken中获取的用户名,
而subject.logout方法,是从subject获取的主身份信息,subject.getPrincipals()
应该是这两个点不同,在深的代码没有继续往下看,有兴趣的可以看看
接下来可以重启,看一下redis中信息
2.使用shiro-redis 插件
简介
大佬就是大佬,简介的确是简介
总之,使用设个插件之后,只需要少量配置,就可以完成shiro整合redis,包括缓存用户信息缓存和会话管理
jar
<dependency>
<groupId>org.iherus.shiro</groupId>
<artifactId>shiro-redis-spring-boot-web-starter</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
注意:引入这两个包之后,删除原先引入的shiro和redis包,否则会冲突
shiroConfig
package com.han.springbootshiro.config.shiro;
import org.apache.shiro.mgt.SessionsSecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.iherus.shiro.cache.redis.RedisCacheManager;
import org.iherus.shiro.cache.redis.RedisSessionDAO;
import org.iherus.shiro.cache.redis.config.RedisStandaloneConfiguration;
import org.iherus.shiro.cache.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPoolConfig;
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(getSessionsSecurityManager());
return shiroFilterFactoryBean;
}
@Bean
public SessionsSecurityManager getSessionsSecurityManager(){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(getRealm());
//将自定义的会话管理器注册到安全管理器中
defaultWebSecurityManager.setSessionManager(sessionManager());
return defaultWebSecurityManager;
}
@Bean
public Realm getRealm(){
ShiroRealm shiroRealm = new ShiroRealm();
//开启redis缓存管理
shiroRealm.setCacheManager(redisCacheManager());
shiroRealm.setCachingEnabled(true); //开启全局缓存
shiroRealm.setAuthenticationCachingEnabled(true); //开启认证缓存
shiroRealm.setAuthorizationCachingEnabled(true); //开启授权缓存
return shiroRealm;
}
/**
* 会话管理器
* @param
* @return
*/
@Bean
public DefaultWebSessionManager sessionManager() {
CustomSessionManager sessionManager = new CustomSessionManager();
// 自定义sessionDAO
sessionManager.setSessionDAO(sessionDAO());
return sessionManager;
}
/**
* <!-- 缓存管理器 -->
* <bean id="cacheManager" class="org.iherus.shiro.cache.redis.RedisCacheManager">
* <property name="connectionFactory" ref="connectionFactory" />
* <property name="database" value="2" />
* <property name="expirationMillis" value="90000" />
* <property name="keyPrefix" value="shiro:cache:" />
* <property name="scanBatchSize" value="3000" />
* <property name="deleteBatchSize" value="5000" />
* <property name="fetchBatchSize" value="50" />
* </bean>
* @return
*/
@Bean
public RedisCacheManager redisCacheManager(){
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setConnectionFactory(connectionFactory());
redisCacheManager.setKeyPrefix("shiro-redis");
return redisCacheManager;
}
/**
* <property name="clientName" value="SRC" />
* <property name="connectTimeoutMillis" value="3000" />
* <property name="soTimeoutMillis" value="2000" />
* <property name="poolConfig" ref="poolConfig" />
* <property name="configuration" ref="standaloneConfiguration" />
* @return
*/
@Bean
public JedisConnectionFactory connectionFactory(){
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
jedisConnectionFactory.setClientName("shiroClient");
jedisConnectionFactory.setPoolConfig(poolConfig());
return jedisConnectionFactory;
}
@Bean
public RedisStandaloneConfiguration redisStandaloneConfiguration(){
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setHost("localhost");
redisStandaloneConfiguration.setPort(6379);
redisStandaloneConfiguration.setDatabase(0);
return redisStandaloneConfiguration;
}
@Bean
public JedisPoolConfig poolConfig(){
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
return jedisPoolConfig;
}
@Bean
public SessionDAO sessionDAO(){
RedisSessionDAO redisSessionDAO = new RedisSessionDAO(redisCacheManager());
return redisSessionDAO;
}
}
官方文档是说
默认情况下,shiro-redis-spring-boot-web-starter会自动创建 RedisCacheManager 和 RedisSessionDAO 实例并注入 WebSecurityManager 和 WebSessionManager中,如果您需要自定义 SecurityManager 和 SessionManager
这块我觉得使用自动注入或者形参注入应该是行的通的,但是没实现,所以按照
Spring集成的方式
创建了bean并注入到了组件中
到现在,shiro整合redis做缓存和会话管理,算是已经结束了
后边加上密码加密就可以了