美文网首页
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