一、问题描述
由前边的学习中了解,用户的角色权限一般存储在数据库中,每次进行权限校验时都要从
数据库查询用户的角色权限信息;对数据库来说这样频繁的查询压力太大了,也影响程序的
性能。
Shiro 中执行权限角色校验时,默认也是优先从缓存中查询用户权限信息,若缓存中查询不到
用户信息,然后才去数据库中查询用户信息。
前边笔记(三)RolesAuthorizationFilter 分析中,AuthorizingRealm.getAuthorizationInfo 方
法中先执行 getAvailableAuthorizationCache() 方法从缓存中查询对应信息,如下图所示:
二、基于JVM内存的权限缓存
Shiro 中虽然默认提供了基于JVM内存的缓存机制,但是默认是没开启,权限角色信息并不会
缓存;因为默认情况下 DefaultWebSecurityManager 中 CacheManager 为null,
如下图所示:
shiro 默认提供的jvm缓存是 MemoryConstrainedCacheManager,它是把权限角色信息缓存到
Map 中,如下所示:
1、如何开启Shiro 缓存
开启 Shiro 缓存功能也很简单,只要把 MemoryConstrainedCacheManager 注入到
DefaultWebSecurityManager 中就行了,示例代码如下:
@Beanpublic DefaultWebSecurityManager securityManager(CustomRealm realm, SessionManager sessionManager ){DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(realm);//将使用的SessionManager注入到SecurityManagersecurityManager.setSessionManager(sessionManager);//使用Shiro 提供的基于JVM 内存的 CacheManagerMemoryConstrainedCacheManager 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> {@Autowiredprivate RedisTemplate redisTemplate;private final String CACHE_PREFIX = "cache:";/*** 获取授权缓存信息* @param k* @return* @throws CacheException*/@Overridepublic 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*/@Overridepublic 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*/@Overridepublic 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*/@Overridepublic void clear() throws CacheException {Set keys = redisTemplate.keys(CACHE_PREFIX + "*");redisTemplate.delete(keys);}/*** 查看有多个权限缓存信息* @return*/@Overridepublic int size() {Set keys = redisTemplate.keys(CACHE_PREFIX + "*");return keys.size();}/*** 获取全部缓存信息的key* @return*/@Overridepublic Set<K> keys() {Set keys = redisTemplate.keys(CACHE_PREFIX + "*");return keys;}/*** 获取全部缓存信息的value* @return*/@Overridepublic 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 {@Autowiredprivate RedisCache redisCache;/*** 返回 Cache 的实现类* @param s* @return* @param <K>* @param <V>* @throws CacheException*/@Overridepublic <K, V> Cache<K, V> getCache(String s) throws CacheException {return redisCache;}
}
3)将自定义的 RedisCacheManager 注入到 DefaultWebSecurityManager 中;
示例代码如下:
@Beanpublic DefaultWebSecurityManager securityManager(CustomRealm realm, SessionManager sessionManager, RedisCacheManager cacheManager){DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(realm);//将使用的SessionManager注入到SecurityManagersecurityManager.setSessionManager(sessionManager);//使用自定义基于Redis 缓存的 RedisCacheManagersecurityManager.setCacheManager(cacheManager);return securityManager;}