Spring Boot + Redis 实现Shiro集群
为实现Web应用的分布式集群部署,要解决登录session的统一。本文利用shiro做权限控制,redis做session存储,结合spring boot快速配置实现session共享。
1、引入相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
2、Redis相关
2.1.redis配置
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
2.2.redis缓存的对象必须序列化,通用序列化
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.serializer.support.DeserializingConverter;
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
/**
redis序列化对象
*/
public class RedisObjectSerializer implements RedisSerializer<Object> {
private Converter<Object, byte[]> serializer = new SerializingConverter();
private Converter<byte[], Object> deserializer = new DeserializingConverter();
static final byte[] EMPTY_ARRAY = new byte[0];public Object deserialize(byte[] bytes) {
if (isEmpty(bytes)) {
return null;
}
try {
return deserializer.convert(bytes);
} catch (Exception ex) {
throw new SerializationException("Cannot deserialize", ex);
}
}public byte[] serialize(Object object) {
if (object == null) {
return EMPTY_ARRAY;
}
try {
return serializer.convert(object);
} catch (Exception ex) {
return EMPTY_ARRAY;
}
}private boolean isEmpty(byte[] data) {
return (data == null || data.length == 0);
}
}
2.3 RedisTemplate 配置
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* redis 配置
*/
@Configuration
public class RedisConfig {
@Bean</br> </span><span style="color: #0000ff">public</span> CacheManager cacheManager(RedisTemplate<Object, Object><span style="color: #000000"> redisTemplate) {</br> RedisCacheManager cacheManager </span>= <span style="color: #0000ff">new</span><span style="color: #000000"> RedisCacheManager(redisTemplate);</br> cacheManager.setDefaultExpiration(</span>1800<span style="color: #000000">);</br> </span><span style="color: #0000ff">return</span><span style="color: #000000"> cacheManager;</br> }</br></br> @Bean</br> </span><span style="color: #0000ff">public</span> RedisTemplate<Object, Object><span style="color: #000000"> redisTemplate(RedisConnectionFactory factory) {</br> RedisTemplate</span><Object, Object> template = <span style="color: #0000ff">new</span> RedisTemplate<Object, Object><span style="color: #000000">();</br> template.setConnectionFactory(factory);</br> template.setKeySerializer(</span><span style="color: #0000ff">new</span><span style="color: #000000"> StringRedisSerializer());</br> template.setValueSerializer(</span><span style="color: #0000ff">new</span><span style="color: #000000"> RedisObjectSerializer());</br> </span><span style="color: #0000ff">return</span><span style="color: #000000"> template;</br> }</br>
}
3.Redis实现shiro的SessionDao存取session
import java.io.Serializable;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;/**
redis实现共享session
*/
@Component
public class RedisSessionDAO extends EnterpriseCacheSessionDAO {private static Logger logger = LoggerFactory.getLogger(RedisSessionDAO.class);
// session 在redis过期时间是30分钟30*60
private static int expireTime = 1800;private static String prefix = "weiyou-shiro-session:";
@Resource
private RedisTemplate<String, Object> redisTemplate;// 创建session,保存到数据库
@Override
protected Serializable doCreate(Session session) {
Serializable sessionId = super.doCreate(session);
logger.debug("创建session:{}", session.getId());
redisTemplate.opsForValue().set(prefix + sessionId.toString(), session);
return sessionId;
}// 获取session
@Override
protected Session doReadSession(Serializable sessionId) {
logger.debug("获取session:{}", sessionId);
// 先从缓存中获取session,如果没有再去数据库中获取
Session session = super.doReadSession(sessionId);
if (session == null) {
session = (Session) redisTemplate.opsForValue().get(prefix + sessionId.toString());
}
return session;
}// 更新session的最后一次访问时间
@Override
protected void doUpdate(Session session) {
super.doUpdate(session);
logger.debug("获取session:{}", session.getId());
String key = prefix + session.getId().toString();
if (!redisTemplate.hasKey(key)) {
redisTemplate.opsForValue().set(key, session);
}
redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
}// 删除session
@Override
protected void doDelete(Session session) {
logger.debug("删除session:{}", session.getId());
super.doDelete(session);
redisTemplate.delete(prefix + session.getId().toString());
}
}
4.实现cache共享
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.springframework.data.redis.core.RedisTemplate;@SuppressWarnings("unchecked")
public class ShiroCache<K, V> implements Cache<K, V> {</span><span style="color: #0000ff">private</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">final</span> String REDIS_SHIRO_CACHE = "weiyou-shiro-cache:"<span style="color: #000000">;</br> </span><span style="color: #0000ff">private</span><span style="color: #000000"> String cacheKey;</br> </span><span style="color: #0000ff">private</span> RedisTemplate<K, V><span style="color: #000000"> redisTemplate;</br> </span><span style="color: #0000ff">private</span> <span style="color: #0000ff">long</span> globExpire = 30<span style="color: #000000">;</br></br>
@SuppressWarnings("rawtypes")
public ShiroCache(String name, RedisTemplate client) {
this.cacheKey = REDIS_SHIRO_CACHE + name + ":";
this.redisTemplate = client;
}
@Override</br> </span><span style="color: #0000ff">public</span> V get(K key) <span style="color: #0000ff">throws</span><span style="color: #000000"> CacheException {</br> redisTemplate.boundValueOps(getCacheKey(key)).expire(globExpire, TimeUnit.MINUTES);</br> </span><span style="color: #0000ff">return</span><span style="color: #000000"> redisTemplate.boundValueOps(getCacheKey(key)).get();</br> }</br> @Override</br> </span><span style="color: #0000ff">public</span> V put(K key, V value) <span style="color: #0000ff">throws</span><span style="color: #000000"> CacheException {</br> V old </span>=<span style="color: #000000"> get(key);</br> redisTemplate.boundValueOps(getCacheKey(key)).set(value);</br> </span><span style="color: #0000ff">return</span><span style="color: #000000"> old;</br> }</br> @Override</br> </span><span style="color: #0000ff">public</span> V remove(K key) <span style="color: #0000ff">throws</span><span style="color: #000000"> CacheException {</br> V old </span>=<span style="color: #000000"> get(key);</br> redisTemplate.delete(getCacheKey(key));</br> </span><span style="color: #0000ff">return</span><span style="color: #000000"> old;</br> }</br></br> @Override</br> </span><span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span> clear() <span style="color: #0000ff">throws</span><span style="color: #000000"> CacheException {</br> redisTemplate.delete(keys());</br> }</br> @Override</br> </span><span style="color: #0000ff">public</span> <span style="color: #0000ff">int</span><span style="color: #000000"> size() {</br> </span><span style="color: #0000ff">return</span><span style="color: #000000"> keys().size();</br> }</br></br> @Override</br> </span><span style="color: #0000ff">public</span> Set<K><span style="color: #000000"> keys() {</br> </span><span style="color: #0000ff">return</span> redisTemplate.keys(getCacheKey("*"<span style="color: #000000">));</br> }</br></br> @Override</br> </span><span style="color: #0000ff">public</span> Collection<V><span style="color: #000000"> values() {</br> Set</span><K> set =<span style="color: #000000"> keys();</br> List</span><V> list = <span style="color: #0000ff">new</span> ArrayList<><span style="color: #000000">();</br> </span><span style="color: #0000ff">for</span><span style="color: #000000"> (K s : set) {</br> list.add(get(s));</br> }</br> </span><span style="color: #0000ff">return</span><span style="color: #000000"> list;</br> }</br></br> </span><span style="color: #0000ff">private</span><span style="color: #000000"> K getCacheKey(Object k) {</br> </span><span style="color: #0000ff">return</span> (K) (<span style="color: #0000ff">this</span>.cacheKey +<span style="color: #000000"> k);</br> }</br>
}
实现shiro 的CacheManager
import javax.annotation.Resource;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.springframework.data.redis.core.RedisTemplate;public class RedisCacheManager implements CacheManager {
@Resource</br> </span><span style="color: #0000ff">private</span> RedisTemplate<String, Object><span style="color: #000000"> redisTemplate;</br></br> @Override</br> </span><span style="color: #0000ff">public</span> <K, V> Cache<K, V> getCache(String name) <span style="color: #0000ff">throws</span><span style="color: #000000"> CacheException {</br> </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">new</span> ShiroCache<K, V><span style="color: #000000">(name, redisTemplate);</br> }</br></br> </span><span style="color: #0000ff">public</span> RedisTemplate<String, Object><span style="color: #000000"> getRedisTemplate() {</br> </span><span style="color: #0000ff">return</span><span style="color: #000000"> redisTemplate;</br> }
public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}}
5.配置
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/**
*
@author April.Chen
*/
//@Configuration
public class ShiroConfig {@Resource
private RedisSessionDAO sessionDAO;@Bean
public UserRealm getUserRealm() {
return new UserRealm();
}@Bean
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}@Bean
public RedisCacheManager redisCacheManager() {
return new RedisCacheManager();
}@Bean
public SessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(sessionDAO);
sessionManager.setGlobalSessionTimeout(1800);
sessionManager.setCacheManager(redisCacheManager());
return sessionManager;
}@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setSessionManager(sessionManager());
securityManager.setCacheManager(redisCacheManager());
return securityManager;
}@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(securityManager());
return new AuthorizationAttributeSourceAdvisor();
}@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
daap.setProxyTargetClass(true);
return daap;
}@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean() {
Map<String, String> filterChainDefinitionMap = new HashMap<>();
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager());
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/index");
filterChainDefinitionMap.put("/sa/", "authc");
filterChainDefinitionMap.put("/", "anon");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
}