一、问题描述
由前边的学习中了解,用户的角色权限一般存储在数据库中,每次进行权限校验时都要从
数据库查询用户的角色权限信息;对数据库来说这样频繁的查询压力太大了,也影响程序的
性能。
Shiro 中执行权限角色校验时,默认也是优先从缓存中查询用户权限信息,若缓存中查询不到
用户信息,然后才去数据库中查询用户信息。
前边笔记(三)RolesAuthorizationFilter 分析中,AuthorizingRealm.getAuthorizationInfo 方
法中先执行 getAvailableAuthorizationCache() 方法从缓存中查询对应信息,如下图所示:
二、基于JVM内存的权限缓存
Shiro 中虽然默认提供了基于JVM内存的缓存机制,但是默认是没开启,权限角色信息并不会
缓存;因为默认情况下 DefaultWebSecurityManager 中 CacheManager 为null,
如下图所示:
shiro 默认提供的jvm缓存是 MemoryConstrainedCacheManager,它是把权限角色信息缓存到
Map 中,如下所示:
1、如何开启Shiro 缓存
开启 Shiro 缓存功能也很简单,只要把 MemoryConstrainedCacheManager 注入到
DefaultWebSecurityManager 中就行了,示例代码如下:
@Bean
public DefaultWebSecurityManager securityManager(CustomRealm realm, SessionManager sessionManager ){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(realm);
//将使用的SessionManager注入到SecurityManager
securityManager.setSessionManager(sessionManager);
//使用Shiro 提供的基于JVM 内存的 CacheManager
MemoryConstrainedCacheManager cacheManager = new MemoryConstrainedCacheManager();
securityManager.setCacheManager(cacheManager);
return securityManager;
}
三、基于Redis的权限缓存
将权限信息存储到内存中在实际工作用有很多限制,如:占用内存资源、只适用于单机环境
、缓存周期短等等。
在实际工作中一般将权限信息存储到中间件Redis 中,实现步骤如下:
1)模仿 MapCache 定义 RedisCache 用于将数据保存到Redis 中,有一点要注意,
RedisCache需要实现 org.apache.shiro.cache 包下的接口 Cache,
示例代码如下:
@Component
public class RedisCache<K,V> implements Cache<K,V> {
@Autowired
private RedisTemplate redisTemplate;
private final String CACHE_PREFIX = "cache:";
/**
* 获取授权缓存信息
* @param k
* @return
* @throws CacheException
*/
@Override
public V get(K k) throws CacheException {
System.out.println("从redis 查询权限信息~~~~~~~~~~~");
V v = (V) redisTemplate.opsForValue().get(CACHE_PREFIX + k);
if(v != null){
redisTemplate.expire(CACHE_PREFIX + k,15, TimeUnit.MINUTES);
}
return v;
}
/**
* 存放缓存信息
* @param k
* @param v
* @return
* @throws CacheException
*/
@Override
public V put(K k, V v) throws CacheException {
redisTemplate.opsForValue().set(CACHE_PREFIX + k,v,15,TimeUnit.MINUTES);
return v;
}
/**
* 清空当前缓存
* @param k
* @return
* @throws CacheException
*/
@Override
public V remove(K k) throws CacheException {
V v = (V) redisTemplate.opsForValue().get(CACHE_PREFIX + k);
if(v != null){
redisTemplate.delete(CACHE_PREFIX + k);
}
return v;
}
/**
* 清空全部的授权缓存
* @throws CacheException
*/
@Override
public void clear() throws CacheException {
Set keys = redisTemplate.keys(CACHE_PREFIX + "*");
redisTemplate.delete(keys);
}
/**
* 查看有多个权限缓存信息
* @return
*/
@Override
public int size() {
Set keys = redisTemplate.keys(CACHE_PREFIX + "*");
return keys.size();
}
/**
* 获取全部缓存信息的key
* @return
*/
@Override
public Set<K> keys() {
Set keys = redisTemplate.keys(CACHE_PREFIX + "*");
return keys;
}
/**
* 获取全部缓存信息的value
* @return
*/
@Override
public Collection<V> values() {
Set values = new HashSet();
Set keys = redisTemplate.keys(CACHE_PREFIX + "*");
for (Object key : keys) {
Object value = redisTemplate.opsForValue().get(key);
values.add(value);
}
return values;
}
}
2)模仿 MemoryConstrainedCacheManager 定义 RedisCacheManager;
RedisCacheManager 实现 org.apache.shiro.cache 包下的接口,并重写 getCache 方法
示例代码如下:
@Component
public class RedisCacheManager implements CacheManager {
@Autowired
private RedisCache redisCache;
/**
* 返回 Cache 的实现类
* @param s
* @return
* @param <K>
* @param <V>
* @throws CacheException
*/
@Override
public <K, V> Cache<K, V> getCache(String s) throws CacheException {
return redisCache;
}
}
3)将自定义的 RedisCacheManager 注入到 DefaultWebSecurityManager 中;
示例代码如下:
@Bean
public DefaultWebSecurityManager securityManager(CustomRealm realm, SessionManager sessionManager, RedisCacheManager cacheManager){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(realm);
//将使用的SessionManager注入到SecurityManager
securityManager.setSessionManager(sessionManager);
//使用自定义基于Redis 缓存的 RedisCacheManager
securityManager.setCacheManager(cacheManager);
return securityManager;
}