以前搞shiro的时候没有刻意去研究过这些配置文件,导致用shiro的时候也是迷迷糊糊,惭愧啊,要想成为人上人,读源码,懂配置是真滴重要!
安全管理器配置(SecurityManager )
配置项 | 意思 | |
一 | setCacheManager | 配置缓存管理器用来缓存realm和session信息 |
二 | setRealm | 登录时假如用的UsernamePasswordToken,会来调用我们这里设置过的realm中找符合条件的realm,执行对应的逻辑 |
三 | setSessionManager | 配置会话管理器 |
/**
* @param
* @method 这里设置了shiroCacheManager,表示要缓存loginRealm,以及会话session。
* 这里如果不设置shiroCacheManager,由于shiroSessionDao(shiroCacheManager)已经注入过shiroCacheManager
* session信息也是能被缓存的
*/
@Bean
public SecurityManager securityManager() {
DefaultSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setCacheManager(shiroCacheManager);
securityManager.setRealm(loginRealm());
securityManager.setSessionManager(sessionManager());
return securityManager;
}
@Bean
public loginRealm loginRealm() {
return new loginRealm();
}
/**
* @param
* @method DefaultWebSessionManager的配置,设置了sessionDAO
*/
@Bean
public SessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(sessionDAO());
return sessionManager;
}
/**
* @param
* @method 这里配置shiro的会话底层的缓存管理器,底层用的是redis缓存会话
*/
@Bean
public SessionDAO sessionDAO() {
shiroSessionDao shiroSessionDao = new shiroSessionDao(shiroCacheManager);
return shiroSessionDao;
}
注意点:
/**
* @param
* @method 这里设置了shiroCacheManager,表示要缓存loginRealm,以及会话session。
* 这里如果不设置shiroCacheManager,由于shiroSessionDao(shiroCacheManager)已经注入过shiroCacheManager
* session信息也是能被缓存的
*/
@Bean
public SecurityManager securityManager() {
DefaultSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setCacheManager(shiroCacheManager);
securityManager.setRealm(loginRealm());
securityManager.setSessionManager(sessionManager());
return securityManager;
}
为什么要配置缓存管理器(setCacheManager)?
不配置缓存管理器: 每次去访问那些需要权限的页面的时候都要执行授权的操作,授权里面又要查权限表,这个页面才会知道到底你是否有权限访问,带来的开销是巨大的。
配置缓存管理器: 只需从缓存里面拿取权限信息就行。
开始配置
首先我们要明白我们要配置的是shiro中的CacheManager
思路:知道这个那就简单了,直接编写其实现类就是了,只不过获取cache的途径是从shiro.cache中获取而已,而shiro.cache中获取cahce的途径是从redis中获取的,怎么从redis获取的?我们直接往shiro.cache中注入一个RedisCacheManager 不就得了。进而实现shiro.cache中的crud,完事。
public class shiroCacheManager implements CacheManager {
private static RedisCacheManager redisCacheManager;
public shiroCacheManager(RedisCacheManager redisCacheManager) {
this.redisCacheManager = redisCacheManager;
}
@Override
public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {
return new shiroCache(redisCacheManager, cacheName);
}
}
自定义Cache<k, v>(shiro提供的cache接口)
根据传进来的 RedisCacheManager 获取对应组的 spring.cache(RedisCacheManager是由Spring提供的,获取到的cache 是spring.cache),然后用这个spring.cache进行crud操作。为了让代码复用性高一点我们用范型来做处理
/**
* @author 张子行
* @class 自定义shiro缓存
*/
public class shiroCache<k, v> implements Cache<k, v> {
private org.springframework.cache.Cache springCache;
private RedisCacheManager redisCacheManager;
private String cacheName;
public shiroCache(RedisCacheManager redisCacheManager, String cacheName) {
this.springCache = redisCacheManager.getCache(cacheName);
this.redisCacheManager = redisCacheManager;
this.cacheName = cacheName;
}
/**
* @param
* @method 缓存的获取
* 在访问需要权限的页面时会,从缓存中拉去权限信息,看你是否有权访问
*/
@Override
public v get(k k) throws CacheException {
System.out.println("get():"+cacheName);
org.springframework.cache.Cache.ValueWrapper valueWrapper = springCache.get(k);
if (valueWrapper != null) {
return (v) valueWrapper.get();
}
return null;
}
/**
* @param
* @method 缓存的放入,在会话开始的时候会把session信息放入缓存,
* 在subject.login()的时候会把授权信息放入缓存
*/
@Override
public v put(k k, v v) throws CacheException {
System.out.println("put():"+cacheName);
springCache.put(k, v);
return v;
}
/**
* @param
* @method 缓存的清除单个(在Subject subject = SecurityUtils.getSubject();
* subject.logout();的时候会清除相应的授权认证缓存,我测试的时候session缓存也会被清除)
*/
@Override
public v remove(k k) throws CacheException {
System.out.println("remove():"+cacheName);
v v = this.get(k);
springCache.evict(k);
return v;
}
/**
* @param
* @method 缓存的全部删除
*/
@Override
public void clear() throws CacheException {
System.out.println("clear(): "+cacheName);
springCache.clear();
}
/**
* @param
* @method 缓存的个数
*/
@Override
public int size() {
System.out.println("size(): "+cacheName);
int size = this.keys().size();
return size;
}
/**
* @param
* @method 缓存的所有key
*/
@Override
public Set<k> keys() {
System.out.println("keys(): "+cacheName);
Collection<String> cacheNames = redisCacheManager.getCacheNames();
return (Set<k>) cacheNames;
}
/**
* @param
* @method 缓存的所有values
*/
@Override
public Collection<v> values() {
System.out.println("values(): "+cacheName);
ArrayList<v> data = new ArrayList<>();
Set<k> keys = this.keys();
for (k k : keys) {
data.add(this.get(k));
}
return data;
}
}
自定义CachingSessionDAO
/**
* @author 张子行
* @class 自定义CachingSessionDAO
*/
public class shiroSessionDao extends CachingSessionDAO {
private static Serializable sessionId = null;
/**
* @param
* @method 提供缓存session的扩展方法,到时候用的时候,
* 想用什么缓存session信息,直接注入对应的CacheManager就行
*/
public shiroSessionDao(CacheManager cacheManager) {
super.setCacheManager(cacheManager);
}
@Override
protected void doUpdate(Session session) {
System.out.println("更新session");
}
@Override
protected void doDelete(Session session) {
System.out.println("删除session");
}
@Override
protected Serializable doCreate(Session session) {
sessionId = "666";
assignSessionId(session, sessionId);
return sessionId;
}
@Override
protected Session doReadSession(Serializable sessionId) {
System.out.println("读取session");
return null;
}
/**
* @param
* @method 把session对象转化为byte保存到redis中
*/
public byte[] sessionToByte(Session session) {
ByteArrayOutputStream bo = new ByteArrayOutputStream();
byte[] bytes = null;
try {
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(session);
bytes = bo.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return bytes;
}
}
注入缓存管理器
/**
* @author 张子行
* @class 缓存管理器
*/
@Configuration
public class cacheConfig {
@Autowired
RedisConnectionFactory redisConnectionFactory;
@Bean(name = "shiroCacheManager")
public shiroCacheManager shiroCacheManager() {
RedisCacheConfiguration conf = RedisCacheConfiguration.defaultCacheConfig();
//shiro配置的缓存管理器是这个,这里是设置登录的过期时间,及其session的过期时间,按秒为单位
conf = conf.entryTtl(Duration.ofSeconds(300000));
RedisCacheManager cacheManager = RedisCacheManager
.builder(redisConnectionFactory)
.cacheDefaults(conf)
.build();
shiroCacheManager shiroCacheManager = new shiroCacheManager(cacheManager);
return shiroCacheManager;
}
@Bean(name = "redisCacheManager")
public RedisCacheManager redisCacheManager() {
RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig();
RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(cacheConfig)
.build();
return redisCacheManager;
}
}
测试
当我们登录的时候(subject.login),发现redis里面确实把 认证信息,session数据写进去了
不断刷新这个页面,发现压根不会进入loginrealm中的授权这,如果没有配置缓存管理器,那么每次刷新都会走授权
接着我们登出的时候也就是执行下面的代码时候,数据成功移除realm和session数据
Subject subject = SecurityUtils.getSubject();
subject.logout();
总结
在集成到shiro中的时候,自己编写 CacheManager 实现对Cache的增删改查会出现很多bug(不建议造轮子),本文的代码也就是为了让大家理解 shiro 的几大核心组件以及缓存生效的配置规则而已。