《跟我学Shiro》笔记11-缓存机制
原文地址:第十一章 缓存机制——《跟我学Shiro》
目录贴: 跟我学Shiro目录贴
源码:https://github.com/zhangkaitao/shiro-example
缓存机制
Shiro 提供了类似于 Spring
的 Cache
抽象,即 Shiro 本身不实现 Cache
,但是对 Cache
进行了又抽象,方便更换不同的底层 Cache
实现。对于 Cache
的一些概念可以参考《Spring Cache抽象详解》。
Shiro 提供的 Cache 接口
public interface Cache<K, V> {
// 根据 Key 获取缓存中的值
public V get(K key) throws CacheException;
// 往缓存中放入 key-value,返回缓存中之前的值
public V put(K key, V value) throws CacheException;
// 移除缓存中 key 对应的值,返回该值
public V remove(K key) throws CacheException;
// 清空整个缓存
public void clear() throws CacheException;
// 返回缓存大小
public int size();
// 获取缓存中所有的 key
public Set<K> keys();
// 获取缓存中所有的 value
public Collection<V> values();
}
Shiro 提供的 CacheManager 接口
public interface CacheManager {
// 根据缓存名字获取一个 Cache
public <K, V> Cache<K, V> getCache(String name) throws CacheException;
}
Shiro 还提供了 CacheManagerAware
用于注入 CacheManager
:
public interface CacheManagerAware {
// 注入 CacheManager
void setCacheManager(CacheManager cacheManager);
}
Shiro 内部相应的组件(DefaultSecurityManager
)会自动检测相应的对象(如 Realm
)是否实现了 CacheManagerAware
并自动注入相应的 CacheManager
。
Realm 缓存
Shiro 提供了 CachingRealm
,其实现了 CacheManagerAware
接口,提供了缓存的一些基础实现;另外 AuthenticatingRealm
及 AuthorizingRealm
分别提供了对 AuthenticationInfo
和 AuthorizationInfo
信息的缓存。
ini配置
[main]
credentialsMatcher=com.github.zhangkaitao.shiro.chapter11.credentials.RetryLimitHashedCredentialsMatcher
credentialsMatcher.hashAlgorithmName=md5
credentialsMatcher.hashIterations=2
credentialsMatcher.storedCredentialsHexEncoded=true
userRealm=com.github.zhangkaitao.shiro.chapter11.realm.UserRealm
userRealm.credentialsMatcher=$credentialsMatcher
;启用缓存,默认 false
userRealm.cachingEnabled=true
;启用身份验证缓存,即缓存 AuthenticationInfo 信息,默认 false
userRealm.authenticationCachingEnabled=true
;缓存 AuthenticationInfo 信息的缓存名称(在 shiro-ehcache.xml 中配置相同名字的缓存)
userRealm.authenticationCacheName=authenticationCache
;启用授权缓存,即缓存 AuthorizationInfo 信息,默认 false
userRealm.authorizationCachingEnabled=true
;缓存 AuthorizationInfo 信息的缓存名称(在 shiro-ehcache.xml 中配置相同名字的缓存)
userRealm.authorizationCacheName=authorizationCache
securityManager.realms=$userRealm
;缓存管理器,此处使用 EhCacheManager,即 Ehcache实现
cacheManager=org.apache.shiro.cache.ehcache.EhCacheManager
cacheManager.cacheManagerConfigFile=classpath:shiro-ehcache.xml
securityManager.cacheManager=$cacheManager
UserRealm
public class UserRealm extends AuthorizingRealm {
private UserService userService = new UserServiceImpl();
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String)principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(userService.findRoles(username));
authorizationInfo.setStringPermissions(userService.findPermissions(username));
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String)token.getPrincipal();
User user = userService.findByUsername(username);
if(user == null) {
throw new UnknownAccountException(); // 没找到帐号
}
if(Boolean.TRUE.equals(user.getLocked())) {
throw new LockedAccountException(); // 帐号锁定
}
// 交给 AuthenticatingRealm 使用 CredentialsMatcher 进行密码匹配,如果觉得人家的不好可以自定义实现
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user.getUsername(), // 用户名
user.getPassword(), // 密码
ByteSource.Util.bytes(user.getCredentialsSalt()), //s alt=username+salt
getName() // realm name
);
return authenticationInfo;
}
@Override
public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
super.clearCachedAuthorizationInfo(principals);
}
@Override
public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
super.clearCachedAuthenticationInfo(principals);
}
@Override
public void clearCache(PrincipalCollection principals) {
super.clearCache(principals);
}
public void clearAllCachedAuthorizationInfo() {
getAuthorizationCache().clear();
}
public void clearAllCachedAuthenticationInfo() {
getAuthenticationCache().clear();
}
// 清空整个缓存
public void clearAllCache() {
clearAllCachedAuthenticationInfo();
clearAllCachedAuthorizationInfo();
}
}
测试用例
@Test
public void testClearCachedAuthenticationInfo() {
login(u1.getUsername(), password);
userService.changePassword(u1.getId(), password + "1");
RealmSecurityManager securityManager = (RealmSecurityManager) SecurityUtils.getSecurityManager();
UserRealm userRealm = (UserRealm) securityManager.getRealms().iterator().next();
userRealm.clearCachedAuthenticationInfo(subject().getPrincipals());
login(u1.getUsername(), password + "1");
}
首先登录成功(此时会缓存相应的 AuthenticationInfo
),然后修改密码;此时密码就变了;接着需要调用 Realm
的 clearCachedAuthenticationInfo
方法清空之前缓存的 AuthenticationInfo
;否则下次登录时还会获取到修改密码之前的那个 AuthenticationInfo
;
// 此处调用 Realm 的 clearCachedAuthorizationInfo 清空之前缓存的 AuthorizationInfo
@Test
public void testClearCachedAuthorizationInfo() {
login(u1.getUsername(), password);
subject().checkRole(r1.getRole());
userService.correlationRoles(u1.getId(), r2.getId());
RealmSecurityManager securityManager = (RealmSecurityManager) SecurityUtils.getSecurityManager();
UserRealm userRealm = (UserRealm) securityManager.getRealms().iterator().next();
userRealm.clearCachedAuthorizationInfo(subject().getPrincipals());
subject().checkRole(r2.getRole());
}
// 同时调用 clearCachedAuthenticationInfo 和 clearCachedAuthorizationInfo,清空 AuthenticationInfo 和 AuthorizationInfo
@Test
public void testClearCache() {
login(u1.getUsername(), password);
subject().checkRole(r1.getRole());
userService.changePassword(u1.getId(), password + "1");
userService.correlationRoles(u1.getId(), r2.getId());
RealmSecurityManager securityManager = (RealmSecurityManager) SecurityUtils.getSecurityManager();
UserRealm userRealm = (UserRealm) securityManager.getRealms().iterator().next();
userRealm.clearCache(subject().getPrincipals());
login(u1.getUsername(), password + "1");
subject().checkRole(r2.getRole());
}
在某些清空下这种方式可能不是最好的选择,可以考虑直接废弃 Shiro 的缓存,然后自己通过如 AOP
机制实现自己的缓存;可以参考:
https://github.com/zhangkaitao/es/tree/master/web/src/main/java/com/sishuok/es/extra/aop
另外如果和 Spring
集成时可以考虑直接使用 Spring
的 Cache
抽象,可以考虑使用 SpringCacheManagerWrapper
,其对 Spring Cache
进行了包装,转换为 Shiro 的 CacheManager
实现:
https://github.com/zhangkaitao/es/blob/master/web/src/main/java/org/apache/shiro/cache/spring/SpringCacheManagerWrapper.java
Session 缓存
securityManager.cacheManager=$cacheManager
sessionManager=org.apache.shiro.session.mgt.DefaultSessionManager
sessionDAO=com.github.zhangkaitao.shiro.chapter11.session.dao.MySessionDAO
sessionDAO.activeSessionsCacheName=shiro-activeSessionCache
securityManager.sessionManager=$sessionManager
如 securityManager
实现了 SessionsSecurityManager
,其会自动判断 SessionManager
是否实现了 CacheManagerAware
接口,如果实现了会把 CacheManager
设置给它。然后 sessionManager
会判断相应的 sessionDAO
(如继承自 CachingSessionDAO
)是否实现了 CacheManagerAware
,如果实现了会把 CacheManager
设置给它;如第九章的 MySessionDAO
就是带缓存的 SessionDAO
;其会先查缓存,如果找不到才查数据库。
shiro-ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="shirocache">
<diskStore path="java.io.tmpdir"/>
<cache name="authorizationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
<cache name="authenticationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
<cache name="shiro-activeSessionCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
</ehcache>
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com
文章标题:《跟我学Shiro》笔记11-缓存机制
文章字数:1.3k
本文作者:Bin
发布时间:2018-04-17, 20:12:22
最后更新:2019-09-02, 17:14:28
原始链接:http://coolview.github.io/2018/04/17/%E8%B7%9F%E6%88%91%E5%AD%A6Shiro/%E3%80%8A%E8%B7%9F%E6%88%91%E5%AD%A6Shiro%E3%80%8B%E7%AC%94%E8%AE%B011-%E7%BC%93%E5%AD%98%E6%9C%BA%E5%88%B6/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。