美文网首页
15. Redis与shiro集群

15. Redis与shiro集群

作者: 不知名的蛋挞 | 来源:发表于2019-08-26 18:48 被阅读0次

    Redis与shiro集群

    进行web处理的服务器很多时候不可能只有一个。用户并不关注我访问了哪台主机,于是此时就会出现一个特别实际的问题:在所有处理过程中,我们所有的认证都由CAS去做,所以此时就会形成一个比较尴尬的局面,CAS就会负责这套集群所有主机的认证,而后这个CAS将会返回票根的认证信息给shiro。用户可能在服务器A产生了session,通过nginx负载均衡之后,票根会返回到服务器B上。在这种情况下,怎么实现不同shiro之间的共享呢?

    所以在实际开发中我们真的用shiro的话我们离不开一个重要的服务器,这个服务器就是redis。利用redis实现所有shiro客户端session的共享。如果现在使用了shiro集群,为了避免出现访问的问题,那么一定需要将所有shiro的session缓存到redis数据库之中,而后shiro的登录分为两部分:登录认证、授权。

    授权数据重复查询没有意义。我们在单实例shiro处理过程中为了解决这个问题我们使用了ecache缓存组件,但是ecache缓存组件有个问题,它只能缓存到一台主机的内存上。所以我们应该把授权数据缓存到redis当中,去redis读取授权数据。

    为了防止出现重复的登录操作,所以本次在进行处理的时候要对两个数据进行缓存:shiro的session处理、shiro角色信息。同时为了方便处理,本次将采用spring-DATA框架针对redis处理。

    使用SpringData操作Redis

    SpringData组件最大的好处是可以将对象自动变为字符串(二进制数据)或者是JSON的结构出现,使用这种模式可以轻松地实现对象与数据流的转换操作。

    如果要想实现redis保存session与角色信息的需求,那么就可以(不是必须,因为可以手工处理)通过Spring-Data提供的支持可以方便地将shiro中的session数据以及相应的角色数据保存到redis里面。

    1. 为项目添加开发包支持

    • 在pom.xml添加Jedis客户端开发包
    • 在pom.xml添加Spring-Data-Redis开发包

    2. redis.properties配置文件

    redis.database=0
    redis.port=6379
    redis.host=192.168.122.205
    redis.password=root
    redis.timeout=1000
    redis.pool.maxTotal=200
    redis.pool.maxIdle=50
    redis.pool.maxWaitMillis=30000
    redis.pool.testOnBorrow=true
    redis.pool.testOnReturn=true
    

    3. spring-redis.xml配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:cache="http://www.springframework.org/schema/cache"
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://www.springframework.org/schema/cache
    http://www.springframework.org/schema/cache/spring-cache.xsd
    http://www.springframework.org/schema/util
    http://www.springframework.org/schema/util/spring-util.xsd">
      <!--配置Annotation的支持操作-->
      <context:annotation-config />
    
      <!--在本程序之中设置要导入的资源文件路径,直接通过classpath加载-->
      <context:property-placeholder location="classpath:redis.properties" ignore-unresolvable="true" />
    
      <!--配置Jedis的连接池的相关项-->
      <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxIdle" value="${redis.pool.maxIdle}"/>
        <property name="maxTotal" value="${redis.pool.maxTotal}"/>
        <property name="maxWaitMillis" value="${redis.pool.maxWaitMillis}"/>
        <property name="testOnBorrow" value="${redis.pool.testOnBorrow}"/>
        <property name="testOnReturn" value="${redis.pool.testOnReturn}"/>
      </bean>
    
      <!--redis服务器中心,主要依靠连接工厂进行操作-->
      <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="database" value="${redis.database}"/>
        <property name="hostName" value="${redis.host}"/>
        <property name="port" value="${redis.port}"/>
        <property name="password" value="${redis.password}"/>
        <property name="poolConfig" ref="jedisPoolConfig"/>
      </bean>
    
      <bean id="redisKeySerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
      <bean id="redisValueSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
    
      <!--提供了redis的连接操作的模板,即:需要将此模板文件注入到代码之中才可以正常执行-->
      <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <!--指定该操作所使用的连接工厂配置-->
        <property name="connectionFactory" ref="jedisConnectionFactory"/>
        <!--序列化key数据的程序类-->
        <property name="keySerializer" ref="redisKeySerializer"/>
        <!--序列化Hash类型的序列器-->
        <property name="hashKeySerializer" ref="redisKeySerializer"/>
        <!--序列化内容的序列器-->
        <property name="valueSerializer" ref="redisValueSerializer"/>
        <!--序列化Hash类型内容的序列器-->
        <property name="hashValueSerializer" ref="redisValueSerializer"/>
      </bean>
    </beans>
    

    4. 进行redis的操作测试

    5. 将spring-redis.xml配置文件导入到applicationContext.xml当中

    使用Redis序列化session

    1. 实现SessionDAO

    SessionDAO的实现最好继承EnterpriseCacheSessionDAO这个父类

    /**
     * redis实现共享session
     */
    @Component
    public class RedisSessionDAO extends EnterpriseCacheSessionDAO {
    
        @Resource
        private RedisTemplate<String, Object> redisTemplate;
    
        // 创建session,保存到数据库
        @Override
        protected Serializable doCreate(Session session) { 
            // // 创建sessionid
            Serializable sessionId = super.doCreate(session);  
            // 将当前创建好的Session的数据保存在Redis数据库里面
            redisTemplate.opsForValue().set(sessionId.toString(), session,1800);
            return sessionId;
        }
    
        // 获取session
        @Override
        protected Session doReadSession(Serializable sessionId) {
            // 先从缓存中获取session,如果没有再去数据库中获取
            Session session = super.doReadSession(sessionId);
            if (session == null) {
                session = (Session) redisTemplate.opsForValue().get(sessionId.toString());
            }
            return session;
        }
    
        // 实现Session更新,每次操作都要更新
        @Override
        protected void doUpdate(Session session) {
            super.doUpdate(session);
            String key = prefix + session.getId().toString();
            if (session!=null) {
                redisTemplate.opsForValue().set(session.getId().toString(), session);
            }
        }
    
        // 删除session
        @Override
        protected void doDelete(Session session) {
            super.doDelete(session);
            redisTemplate.delete(session.getId().toString());
        }
    }
    

    2. 修改applicationContext.xml配置文件

    <bean id="sessionDAO" class="cn.web.session.RedisSessionDAO">
         <!--设置session缓存的名字,这个名字可以任意-->
         <property name="activeSessionsCacheName" value="shiro-activeSessionCache">
         <!--定义该Session DAO操作中所使用的ID生成器-->
         <property name="sessionIdGenerator" ref="shiro-sessionIdGenerator">
    </bean>
    

    那么此时在分布式集群中所有可以使用的Session都会保存在Redis里面。由于Shiro有自己的Session实现机制,所以在分布式集群之中,此类模式一定是必然的选择。

    利用Redis序列化角色与权限数据

    在之前学习Shiro的时候由于只是单WEB端,所以所有的角色与权限都序列化到了内存里面,使用了EHCache缓存组件完成,具体的配置如下:

    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
       <property name="cacheManagerConfigFile" value="classpath:ehcache.xml">
    </bean>
    

    但是现在是属于服务器集群的环境下,那么现在如果将某一个角色或权限只是保存在了一台WEB服务器的内存里面,那么其他的服务器如果需要角色或权限还需要进行一次查询。

    现在最好的做法就是将角色和权限的数据序列化到redis数据库里面,这样就不需要每一个的web端进行重复的查询处理了。

    1. 首先如果要想将数据序列化到redis数据库之中,那么现在就必须有一个与之匹配的Cache处理类,处理CRUD

    import org.apache.shiro.cache.Cache;
    import org.apache.shiro.cache.CacheException;
    import org.springframework.dao.DataAccessException;
    import org.springframework.data.redis.connection.RedisConnection;
    import org.springframework.data.redis.core.RedisCallback;
    import org.springframework.data.redis.core.RedisTemplate;
    import java.util.Collection;
    import java.util.HashSet;
    import java.util.Iterator;
    import java.util.Set;
    
    public class RedisCache<K,V> implements Cache<K,V> {
    
        private RedisTemplate<String, Object> redisTemplate;
    
        public RedisCache(RedisTemplate<String, Object> redisTemplate) {
            this.redisTemplate = redisTemplate;
        }
    
        @Override
        public V get(K key) throws CacheException {
            return (V)this.redisTemplate.opsForValue().get(key.toString());
        }
    
        @Override
        public V put(K key, V value) throws CacheException {
            this.redisTemplate.opsForValue().set(key.toString(),value);
            return value;
        }
    
        @Override
        public V remove(K key) throws CacheException {
            V val = this.get(key);
            this.redisTemplate.delete(key.toString());
            return val;
        }
    
        @Override
        public void clear() throws CacheException {
            this.redisTemplate.execute(new RedisCallback<Boolean>() {
                @Override
                public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                    connection.flushDb();
                    return true;
                }
            });
        }
    
        @Override
        public int size() {
            return this.redisTemplate.execute(new RedisCallback<Integer>() {
                @Override
                public Integer doInRedis(RedisConnection connection) throws DataAccessException {
                    return connection.keys("*".getBytes()).size();
                }
            });
        }
    
        @Override
        public Set<K> keys() {
            return this.redisTemplate.execute(new RedisCallback<Set<K>>() {
                @Override
                public Set<K> doInRedis(RedisConnection connection) throws DataAccessException {
                    Set<K> set = new HashSet<>();
                    Set<byte[]> keys = connection.keys("*".getBytes());
                    Iterator<byte[]> iter = keys.iterator();
                    while (((Iterator) iter).hasNext()){
                        set.add((K)((Iterator) iter).next());
                    }
                    return set;
                }
            });
        }
    
        @Override
        public Collection<V> values() {
            return this.redisTemplate.execute(new RedisCallback<Set<V>>() {
                @Override
                public Set<V> doInRedis(RedisConnection connection) throws DataAccessException {
                    Set<V> set = new HashSet<>();
                    Set<byte[]> keys = connection.keys("*".getBytes());
                    Iterator<byte[]> iter = keys.iterator();
                    while (((Iterator) iter).hasNext()){
                        set.add((V)connection.get(iter.next()));
                    }
                    return set;
                }
            });
        }
    }
    

    2. 所有的Cache接口的对象一定要通过CacheManager进行处理

    import org.apache.shiro.cache.Cache;
    import org.apache.shiro.cache.CacheManager;
    import org.springframework.data.redis.core.RedisTemplate;
    import java.util.concurrent.ConcurrentHashMap;
    
    public class RedisCacheManager implements CacheManager {
    
        // CacheManager负责所有数据的缓存,那么对于数据而言,应该保存在缓存里面
        private final ConcurrentHashMap<String,Cache> caches = new ConcurrentHashMap<>();
    
        private RedisTemplate<String, Object> redisTemplate;
    
        public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
            this.redisTemplate = redisTemplate;
        }
    
        @Override
        public Cache getCache(String name) {
            // 通过Map取的Cache
            Cache<Object,Object> cache = this.caches.get(name);
            if(cache==null){  // 当前集合里面没有Cache的数据
                cache = new RedisCache(redisTemplate);  // 实例化一个新的Cache
                this.caches.put(name,cache);
            }
            return cache;
        }
    }
    
    1. 修改applicationContext.xml文件
    <bean id="securityManager" class="com.vip.poc.backend.vpao.cas.VpaoVisSecurityManager">
            <property name="cacheManager" ref="redisCacheManager"/>
            <!--其他配置略-->
    </bean>
    
    <!--<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
       <property name="cacheManagerConfigFile" value="classpath:ehcache.xml">
    </bean>-->
    
    <bean id="redisCacheManager" class="org.apache.shiro.cache.RedisCacheManager">
       <property name="cacheManagerConfigFile" value="classpath:ehcache.xml">
    </bean>
    

    相关文章

      网友评论

          本文标题:15. Redis与shiro集群

          本文链接:https://www.haomeiwen.com/subject/rjgksctx.html