《跟我学Shiro》笔记11-缓存机制

  1. 缓存机制
    1. Shiro 提供的 Cache 接口
    2. Shiro 提供的 CacheManager 接口
  2. Realm 缓存
    1. ini配置
    2. UserRealm
    3. 测试用例
  3. Session 缓存
  4. shiro-ehcache.xml

原文地址:第十一章 缓存机制——《跟我学Shiro》
目录贴: 跟我学Shiro目录贴
源码:https://github.com/zhangkaitao/shiro-example

缓存机制

Shiro 提供了类似于 SpringCache 抽象,即 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 接口,提供了缓存的一些基础实现;另外 AuthenticatingRealmAuthorizingRealm 分别提供了对 AuthenticationInfoAuthorizationInfo 信息的缓存。

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),然后修改密码;此时密码就变了;接着需要调用 RealmclearCachedAuthenticationInfo 方法清空之前缓存的 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 集成时可以考虑直接使用 SpringCache 抽象,可以考虑使用 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" 转载请保留原文链接及作者。

目录