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


声明:本文转载自https://my.oschina.net/qwzhang01/blog/1620339,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

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

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

思路

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

实现步骤

  1. 设置项目缓存为Redis,这样Spring项目的缓存就都会存在Redis
  2. 设置应用session由Shiro托管
  3. 实现Shiro的缓存管理器CacheManger接口,将Spring应用缓存管理器注入shiro缓存管理器,这样shiro的缓存都由Spring处理
  4. 实现Shiro的Cache接口,将Spring的缓存工具类注入,使Shiro对缓存信息的存取由Spring的缓存实现
  5. 实现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<String, Object> redisTemplate() {         RedisTemplate<String, Object> 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<String, Cache> caches = new ConcurrentHashMap<String, Cache>();     // 注入Spring的缓存管理器     @Autowired     private org.springframework.cache.CacheManager cacheManager;      @Override     public <K, V> Cache<K, V> 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<K, V> implements Cache<K, V> {      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<K> keys() {         throw new RuntimeException("");     }      @Override     public Collection<V> values() {         throw new RuntimeException("");     } }

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

 import com.canyou.bkcell.common.kit.ServletKit; import org.apache.shiro.cache.Cache; import org.apache.shiro.session.Session; import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;  import javax.servlet.http.HttpServletRequest; import java.io.Serializable;  public class RedisSessionDao extends EnterpriseCacheSessionDAO {      private Cache cache() {         Cache<Object, Object> cache = getCacheManager().getCache(this.getClass().getName());         return cache;     }      @Override     protected Serializable doCreate(Session session) {         Serializable sessionId = super.doCreate(session);         cache().put(sessionId.toString(), session);         return sessionId;     }      @Override     protected Session doReadSession(Serializable sessionId) {         Session session = null;         HttpServletRequest request = ServletKit.getRequest();         if (request != null){             String uri = request.getServletPath();             if (ServletKit.isStaticFile(uri)){                 return null;             }             session = (Session)request.getAttribute("session_"+sessionId);         }         if (session == null) {             session = super.doReadSession(sessionId);         }         if (session == null) {             session = (Session) cache().get(sessionId.toString());         }         return session;     }      @Override     protected void doUpdate(Session session) {         HttpServletRequest request = ServletKit.getRequest();         if (request != null) {             String uri = request.getServletPath();             if (ServletKit.isStaticFile(uri)) {                 return;             }         }         super.doUpdate(session);         cache().put(session.getId().toString(), session);     }      @Override     protected void doDelete(Session session) {         super.doDelete(session);         cache().remove(session.getId().toString());     } }

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

6. 简单优化,减少session的redis读取次数

shiro的session存在redis里面后,一次Request对session有很多次读取操作,同时静态资源的访问等都会读取session,虽然redis的性能与内存一样,但是redis毕竟存在网络传输的过程。因此在sessionDAO里面优化的session读操作,减少不必要的在redis读取次数。

1) 优化思路

  • 过滤静态资源,请求静态资源的时候不读取session
  • 读取session先通过Request域获取,如果Request域中不存时,再通过Redis读取获取session。

2)实现步骤及具体实现

    1.添加Servlet工具类,实现在任意位置获取Request,添加判断请求uri是否是静态资源方法。

 import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.servlet.resource.ResourceUrlProvider;  import javax.servlet.http.HttpServletRequest;  public class ServletKit {     public static HttpServletRequest getRequest() {         return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();     }      public static boolean isStaticFile(String uri) {         ResourceUrlProvider resourceUrlProvider = SpringContextKit.getBean(ResourceUrlProvider.class);         String staticUri = resourceUrlProvider.getForLookupPath(uri);         return staticUri != null;     } } 

    2.在sessionDao的doReadSession操作中过滤静态资源代码,请求uri如果是静态资源,session返回null;读取session操作先获取Request域中的session,如果获取不到,再读取Redis缓存。

@Override     protected Session doReadSession(Serializable sessionId) {         Session session = null;         // 获取本次Request         HttpServletRequest request = ServletKit.getRequest();         if (request != null){             String uri = request.getServletPath();             // 过滤静态资源请求             if (ServletKit.isStaticFile(uri)){                 return null;             }             // 在Request域中获取session             session = (Session)request.getAttribute("session_"+sessionId);         }         if (session == null) {             session = super.doReadSession(sessionId);         }         if (session == null) {             session = (Session) cache().get(sessionId.toString());         }         return session;     }

 

本文发表于2018年02月09日 10:32
(c)注:本文转载自https://my.oschina.net/qwzhang01/blog/1620339,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 2724 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1