使用Redis缓存Shiro授权认证信息,搭建集群权限系统

2018-02-09 12:53:23来源:oschina作者:张奇文人点击

分享

应用如果做负载均衡,集群间session需要共享,如果session没有共享,用户登录系统以后session保存在登录的应用里面,其他应用里面没有session,没有登陆状态,访问会失败。下面介绍一个SpringBoot下面基于Shiro的session共享方案。


方案的全部代码在GitHub上面。https://github.com/qwzhang01/bkcell_security


思路

使用Shiro托管应用session
使用Redis管理Shiro缓存

实现步骤

设置项目缓存为Redis,这样Spring项目的缓存就都会存在Redis
设置应用session由Shiro托管
实现Shiro的缓存管理器CacheManger接口,将Spring应用缓存管理器注入shiro缓存管理器,这样shiro的缓存都由Spring处理
实现Shiro的Cache接口,将Spring的缓存工具类注入,使Shiro对缓存信息的存取由Spring的缓存实现
实现Shiro的EnterpriseCacheSessionDAO类,重写Shiro对于session的CRUD,使用重写的Shiro的Cache接口,对session的CRUD在Redis中处理

具体实现


1. 配置Redis


在application.properties文件中添加如下内容,配置Redis的host 密码 端口号等


spring.redis.host=192.168.10.135
spring.redis.port=6379
spring.redis.password=000000

添加Redis缓存配置类


import com.canyou.bkcell.common.kit.PropKit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
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.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Autowired
private RedisConnectionFactory factory;
@Override
@Bean
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
};
}
@Bean
public CacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
rcm.setDefaultExpiration(PropKit.getInt("spring.redis.timeout") * 60);
return rcm;
}
@Bean
public RedisTemplate redisTemplate() {
RedisTemplate redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer(this.getClass().getClassLoader()));
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
}

通过以上两步,应用的缓存实现使用Redis。


2. 配置Shiro,由Shiro托管应用session


在Shiro的SecurityManager中注入SessionManager


@Bean
public DefaultWebSessionManager sessionManager() {
ShiroSessionManager sessionManager = new ShiroSessionManager();
sessionManager.setSessionDAO(sessionDao());
sessionManager.setSessionIdUrlRewritingEnabled(false);
//设置session过期时间为1小时(单位:毫秒),默认为30分钟
sessionManager.setGlobalSessionTimeout(PropKit.getInt("spring.redis.session.timeout") * 60 * 1000);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setCacheManager(shiroRedisCacheManager());
sessionManager.setSessionValidationSchedulerEnabled(false);
Cookie sessionIdCookie = sessionManager.getSessionIdCookie();
sessionIdCookie.setPath("/");
sessionIdCookie.setName("csid");
sessionManager.setSessionIdCookieEnabled(true);
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(authRealm());
securityManager.setCacheManager(shiroRedisCacheManager());
// 设置通过shiro管理应用session
securityManager.setSessionManager(sessionManager());
return securityManager;
}

3. 实现Shiro的缓存管理器CacheManger接口,将Spring应用缓存管理器注入shiro缓存管理器,这样shiro的缓存都由Spring处理


import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@Component
@Qualifier("shiroRedisCacheManager")
public class ShiroRedisCacheManager implements CacheManager {
private final ConcurrentMap caches = new ConcurrentHashMap();
// 注入Spring的缓存管理器
@Autowired
private org.springframework.cache.CacheManager cacheManager;
@Override
public Cache getCache(String name) throws CacheException {
Cache cache = caches.get(name);
if (cache == null) {
org.springframework.cache.Cache springCache = cacheManager.getCache(name);
// 通过spring的缓存管理器,获取缓存,将缓存注入Redis的缓存中
cache = new ShiroRedisCache(springCache);
caches.put(name, cache);
}
return cache;
}
}

4. Shiro的缓存类


import com.canyou.bkcell.common.kit.ByteKit;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import java.util.Collection;
import java.util.Set;
public class ShiroRedisCache implements Cache {
private String keyPrefix = "shiro_redis_session:";
private org.springframework.cache.Cache cache;
public ShiroRedisCache(org.springframework.cache.Cache springCache) {
this.cache = springCache;
}
public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}
private String genKey(K key) {
return (keyPrefix + new String(ByteKit.toByte(key)));
}
@Override
public V get(K key) throws CacheException {
if (key == null) {
return null;
}
org.springframework.cache.Cache.ValueWrapper valueWrapper = cache.get(genKey(key));
if (valueWrapper == null) {
return null;
}
V v = (V) valueWrapper.get();
return v;
}
@Override
public V put(K key, V value) throws CacheException {
cache.put(genKey(key), value);
return value;
}
@Override
public V remove(K key) throws CacheException {
V v = (V) cache.get(genKey(key)).get();
cache.evict(genKey(key));
return v;
}
@Override
public void clear() throws CacheException {
cache.clear();
}
@Override
public int size() {
throw new RuntimeException("");
}
@Override
public Set keys() {
throw new RuntimeException("");
}
@Override
public Collection values() {
throw new RuntimeException("");
}
}

5. 重写SessionDAO,实现session的CRUD功能


import com.canyou.bkcell.common.kit.PropKit;
import org.apache.shiro.mgt.SecurityManager;
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.servlet.Cookie;
import org.apache.shiro.web.servlet.SimpleCookie;
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;
import org.springframework.context.annotation.DependsOn;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean
public RedisSessionDao sessionDao() {
RedisSessionDao sessionDao = new RedisSessionDao();
sessionDao.setCacheManager(shiroRedisCacheManager());
return sessionDao;
}
@Bean
public Cookie cookie(){
Cookie cookie = new SimpleCookie("JSESSIONID");
cookie.setDomain(PropKit.getStr("session.cookie.domain"));
return cookie;
}
@Bean
public DefaultWebSessionManager sessionManager() {
ShiroSessionManager sessionManager = new ShiroSessionManager();
sessionManager.setSessionDAO(sessionDao());
sessionManager.setSessionIdUrlRewritingEnabled(false);
//设置session过期时间为1小时(单位:毫秒),默认为30分钟
sessionManager.setGlobalSessionTimeout(PropKit.getInt("spring.redis.session.timeout") * 60 * 1000);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setCacheManager(shiroRedisCacheManager());
sessionManager.setSessionValidationSchedulerEnabled(false);
Cookie sessionIdCookie = sessionManager.getSessionIdCookie();
sessionIdCookie.setPath("/");
sessionIdCookie.setName("csid");
sessionManager.setSessionIdCookieEnabled(true);
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
/**
* 缓存管理器,缓存都从这里取
*
* @return
*/
@Bean(name = "shiroRedisCacheManager")
public ShiroRedisCacheManager shiroRedisCacheManager() {
ShiroRedisCacheManager shiroRedisCacheManager = new ShiroRedisCacheManager();
return shiroRedisCacheManager;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(authRealm());
securityManager.setCacheManager(shiroRedisCacheManager());
// 设置通过shiro管理应用session
securityManager.setSessionManager(sessionManager());
return securityManager;
}
@Bean
public AuthRealm authRealm() {
AuthRealm authRealm = new AuthRealm();
authRealm.setCacheManager(shiroRedisCacheManager());
authRealm.setCachingEnabled(true);
authRealm.setAuthenticationCachingEnabled(true);
authRealm.setAuthorizationCachingEnabled(true);
return authRealm;
}
/**
* shiro 的filter链
*
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map filterMap = new HashMap<>();
filterMap.put("authc_kisso", new KissoFilter());
filterMap.put("shiro_kisso", new KiSsoShiroAuthFilter());
filterMap.put("logout_kisso", new KiSsoShiroLogoutFilter());
shiroFilterFactoryBean.setFilters(filterMap);
Map filterChainDefinitionMap = new LinkedHashMap();
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/fonts/**", "anon");
filterChainDefinitionMap.put("/login/logout", "logout_kisso");
filterChainDefinitionMap.put("/login/**", "anon");
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
//:这是一个坑呢,一不小心代码就不好使了;
//
filterChainDefinitionMap.put("/**", "authc_kisso, shiro_kisso");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授权界面;spring boot 不管用,不造为啥
// shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
}

致此,完成使用Redis缓存Shiro授权认证信息,搭建集群权限系统

微信扫一扫

第七城市微信公众平台