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;
}
}
- 修改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>
网友评论