zoukankan      html  css  js  c++  java
  • Apache-Shiro+Zookeeper系统集群安全解决方案之缓存管理

    上篇【Apache-Shiro+Zookeeper系统集群安全解决方案之会话管理】,解决了Shiro在系统集群开发时安全的会话共享问题,系统在使用过程中会有大量的权限检查和用户身份检验动作,为了不频繁访问储存容器,Shiro提供了三个缓存机制:

    1. 用户登录Session缓存,默认是不开启的,在Realm配置中设置authenticationCachingEnabled开启,
      使用原则是用户在登录成功后缓存登录校验信息,如 admin 登录成功后将用户名密码等缓存,在超时退出或直接关闭浏览器需要重新登录时不访问数据库。
      在用户退出或超时自动清理缓存数据。
      缓存数据通过配置的cacheManager存储。

    2. 用户权限缓存,用户访问到在配置URL规则范围内的链接时触发读取当前用户的权限并缓存
      同样缓存数据是通过配置的cacheManager存储。

    3. 还有一个是SessionDao中实现的用户登录后的身份信息缓存到本地比如内存,在一定时间内不会到Session存储容器中取用户信息,可以减少Shiro对存储容器的大量读取,在上文有提到并有实现方法。

    所以本文的说到解决方案是Shiro + Zookeeper,Shiro权限框架,利用Zookeeper做存储容器。

    用到的框架技术:

    Spring + Shiro + Zookeeper

    SHIRO整合SPRING配置

    applicationContext-shiro.xml 伪代码:

    <!-- 自定义shiro的realm -->
    <bean id="jdbcAuthenticationRealm" class="..jdbcAuthenticationRealm" depends-on="...">
        ...
        <!-- 自定义缓存名 -->
        <property name="authorizationCacheName" value="shiroAuthorizationCache"/>
        <property name="authenticationCacheName" value="shiroAuthenticationCache"/>
        <!--
        缓存用户登录信息,默认不缓存
        缓存后用户在不点退出的情况下再次登录直接使用缓存中数据
        注意修改密码后要自动退出用户, 否则用户直接关闭浏览器下次使用缓存数据将无法登录!
        -->
        <property name="authenticationCachingEnabled" value="true"/>
        <!--缓存用户权限信息-->
        <property name="authorizationCachingEnabled" value="true"/>
    </bean>
    
    <!-- 缓存配置 -->
    <bean id="zkShiroSessionCache" class="..ZKShiroCacheManager">
        <property name="zookeeperTemplate" ref="zookeeperTemplate"/>
        <!-- 结尾不加/ -->
        <property name="shiroSessionCacheZKPath" value="/SHIROSESSIONCACHE"/>
        <property name="sessionCachePrefix" value="cache-"/>
    </bean>
    
    <!-- SHIRO安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="jdbcAuthenticationRealm"/>
        <property name="cacheManager" ref="zkShiroSessionCache"/>
        <property name="sessionManager" ref="sessionManager"/>
    </bean>
    

    缓存管理器

    Shiro会通过缓存管理器和缓存名获取缓存DAO,比如上面配置的shiroAuthorizationCacheshiroAuthenticationCache会产生两个缓存DAO用来存取不同类型的缓存数据。

    ZKShiroCacheManager.java

    import bgonline.foundation.hadoop.zk.IZookeeperTemplate;
    import org.apache.shiro.ShiroException;
    import org.apache.shiro.cache.Cache;
    import org.apache.shiro.cache.CacheException;
    import org.apache.shiro.cache.CacheManager;
    import org.apache.shiro.util.Destroyable;
    import org.apache.shiro.util.Initializable;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.util.Assert;
    
    /**
     * SHIRO缓存Manager
     *
     */
    public class ZKShiroCacheManager implements CacheManager, Initializable, Destroyable {
    
        Logger logger = LoggerFactory.getLogger(this.getClass());
    
        /**
         * zk操作工具类
         */
        private IZookeeperTemplate zookeeperTemplate;
    
        /**
         * 缓存根路径
         */
        private String shiroSessionCacheZKPath = "/SHIROSESSIONS2";
    
        /**
         * 缓存项前缀
         */
        private String sessionCachePrefix = "cache-";
    
        @Override
        public void destroy() throws Exception {
        }
    
        /**
         * 根据名字返回CACHE
         * @param s
         * @param <K>
         * @param <V>
         * @return
         * @throws CacheException
         */
        @Override
        public <K, V> Cache<K, V> getCache(String s) throws CacheException {
            logger.info("shiro get cache, return : ZKShiroCache, cache name: {}", s);
            Assert.notNull(zookeeperTemplate, "zookeeperTemplate must be set !");
            return new ZKShiroCache<K, V>(zookeeperTemplate, shiroSessionCacheZKPath, sessionCachePrefix, s);
        }
    
        @Override
        public void init() throws ShiroException {
        }
    
        public void setZookeeperTemplate(IZookeeperTemplate zookeeperTemplate) {
            this.zookeeperTemplate = zookeeperTemplate;
        }
    
        public void setShiroSessionCacheZKPath(String shiroSessionCacheZKPath) {
            this.shiroSessionCacheZKPath = shiroSessionCacheZKPath;
        }
    
        public void setSessionCachePrefix(String sessionCachePrefix) {
            this.sessionCachePrefix = sessionCachePrefix;
        }
    }
    

    缓存DAO

    真正的缓存CRUD等操作类。

    ZKShiroCache.java

    import bgonline.foundation.hadoop.zk.IZookeeperTemplate;
    import bgonline.foundation.hadoop.zk.ZNode;
    import org.apache.shiro.cache.Cache;
    import org.apache.shiro.cache.CacheException;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.util.SerializationUtils;
    
    import java.util.*;
    
    /**
     * SHIRO集群缓存储存类
     *
     */
    public class ZKShiroCache<K, V> implements Cache<K, V> {
    
        Logger logger = LoggerFactory.getLogger(this.getClass());
    
        private IZookeeperTemplate zookeeperTemplate;
        private String shiroSessionCacheZKPath;
        private String sessionCachePrefix;
        private String cacheName;
    
        /**
         * 根据cacheName创建ZK CACHE 实例
         *
         * @param zookeeperTemplate       zk操作工具类
         * @param shiroSessionCacheZKPath 缓存根路径, 结尾不加/
         * @param sessionCachePrefix      缓存项前缀
         * @param cacheName               缓存名字
         */
        public ZKShiroCache(IZookeeperTemplate zookeeperTemplate, String shiroSessionCacheZKPath, String sessionCachePrefix, String cacheName) {
            this.zookeeperTemplate = zookeeperTemplate;
            this.shiroSessionCacheZKPath = shiroSessionCacheZKPath;
            this.sessionCachePrefix = sessionCachePrefix;
            this.cacheName = cacheName;
        }
    
        @Override
        public V get(K k) throws CacheException {
            String path = getPath(k);
            logger.debug("get cache for key: {}", path);
    
            byte[] byteValue = zookeeperTemplate.getData(path).getByteData();
            if (byteValue.length > 0)
                return (V) SerializationUtils.deserialize(byteValue);
            else
                return null;
        }
    
        @Override
        public V put(K k, V v) throws CacheException {
            V previous = get(k);
            String path = getPath(k);
            ZNode node = new ZNode();
            node.setPath(path);
            node.setByteData(SerializationUtils.serialize(v));
            String nodepath = zookeeperTemplate.createNode(node);
            logger.debug("put cache for key: {}, return path: {}", path, nodepath);
            return previous;
        }
    
        @Override
        public V remove(K k) throws CacheException {
            String path = getPath(k);
            logger.debug("remove cache for key: {}", path);
            V previous = get(k);
            zookeeperTemplate.deleteNode(path);
            return previous;
        }
    
        @Override
        public void clear() throws CacheException {
            logger.debug("clear cache");
            zookeeperTemplate.deleteNode(shiroSessionCacheZKPath);
        }
    
        @Override
        public int size() {
            ZNode node = new ZNode();
            node.setPath(shiroSessionCacheZKPath);
            int size = zookeeperTemplate.getChildren(node).size();
            logger.debug("get cache size: {}", size);
            return size;
        }
    
        @Override
        public Set<K> keys() {
            List<String> cl = cacheList();
            logger.debug("get cache keys, size : {}", cl.size());
            return (Set<K>) cl;
        }
    
        @Override
        public Collection<V> values() {
            Collection<V> cs = new ArrayList<V>();
            List<String> cl = cacheList();
            for (String k : cl) {
                //读取的key形如: cache-shiroAuthenticationCacheadmin
                if (k.startsWith(sessionCachePrefix)) {
                    String noPrefixId = k.replace(sessionCachePrefix, "");
                    String path = getPath(noPrefixId);
                    cs.add((V) zookeeperTemplate.getData(path).getByteData());
                }
            }
            logger.debug("get cache values, size : {}", cs.size());
            return cs;
        }
    
        /**
         * 获取所有缓存列表
         *
         * @return list
         */
        private List<String> cacheList() {
            ZNode node = new ZNode();
            node.setPath(shiroSessionCacheZKPath);
            List<String> children = zookeeperTemplate.getChildren(node);
            logger.debug("get cache list, size : {}", children.size());
            return children;
        }
    
        /**
         * 生成缓存项存储全路径KEY
         * 根路径/缓存前缀+缓存名字+缓存KEY
         *
         * @param key
         * @return
         */
        private String getPath(Object key) {
            return shiroSessionCacheZKPath + '/' + sessionCachePrefix + cacheName + key;
        }
    }
    

    小结

    Shiro提供了很全面的接口,方便我们根据自己的需要任意扩展,关于自定义缓存还可以参考Shiro自带的MemoryConstrainedCacheManagerMapCache 这也是一个非常好的例子。

    文中提到几个自己封装的有关Zookeeper的类可以替换成自己的实现类,或者也可能直接使用其它的工具做存储容器比如数据库等,本文主要是起到抛砖引玉的作用,说明Shiro在缓存共享自定义实现的过程和配置。

  • 相关阅读:
    正确显示textarea中输入的回车和空格
    HmacSHA256算法(C# 和 Java)
    Java RSA分段加密
    穿越古代我能做啥?
    DOS常用命令
    C#泛型学习
    一步一步搭建Nuget私服
    深入理解HTTP协议
    PowerDesigner设置code和name不联动的方法
    log4net通过代码控制按分类输出
  • 原文地址:https://www.cnblogs.com/xguo/p/3222749.html
Copyright © 2011-2022 走看看