`
y806839048
  • 浏览: 1127508 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

Shiro通过Redis管理会话实现集群

阅读更多

流程概要说明

1.Servlet容器在用户浏览器首次访问后会产生Session,并将Session的ID保存到Cookie中(浏览器不同key不一定相同),同时Shiro会将该Session缓存到Redis中; 

 

2.用户登录认证成功后Shiro会修改Session属性,添加用户认证成功标识,并同步修改Redis中Session;

 

3.用户发起请求后,Shiro会先判断本地EhCache缓存中是否存在该Session,如果有,直接从本地EhCache缓存中读取,如果没有再从Redis中读取Session,并在此时判断Session是否认证通过,如果认证通过将该Session缓存到本地EhCache中; 

 

4.如果Session发生改变,或被删除(用户退出登录),先对Redis中Session做相应修改(修改或删除);再通过Redis消息通道发布缓存失效消息,通知其它节点EhCache失效。

1.S

 

   注意:

 

在session创建之后,才会包sessionid传到客户端作为cookie,(通过共享)已经有(没有重新创建)不会生成新的sessionid,此时浏览器请求带的cookie还是之前其他服务第一次生成session时

对应对的sessionid,从这里看不论是ha服务还是分布式子系统,用redis共享session和cas一样即可实现单点登录(不同子系统),也可实现ha(同一系统的服务转移)

 

我们可以从很多网站的登录界面中看到“请记住我”这样的选项,如果你勾选了它之后再登录,那么在下一次访问该网站的时候就不需要进行重复而繁琐的登录动作了,而这个功能就是通过Cookie实现的

 

参考:

https://www.cnblogs.com/andy-zhou/p/5360107.html

写在前面

1.在上一篇帖子 Shiro一些补充 中提到过Shiro可以使用Shiro自己的Session或者自定义的Session来代替HttpSession

2.Redis/Jedis参考我写的 http://sgq0085.iteye.com/category/317384 一系列内容

 

 

 

一. SessionDao

配置在sessionManager中,可选项,如果不修改默认使用MemorySessionDAO,即在本机内存中操作。

如果想通过Redis管理Session,从这里入手。只需要实现类似DAO接口的CRUD即可。

经过1:最开始通过继承AbstractSessionDAO实现,发现doReadSession方法调用过于频繁,所以改为通过集成CachingSessionDAO来实现。

注意,本地缓存通过EhCache实现,失效时间一定要远小于Redis失效时间,这样本地失效后,会访问Redis读取,并重新设置Redis上会话数据的过期时间。

 

因为Jedis API KEY和Value相同,同为String或同为byte[]为了方便扩展下面的方法

 

Java代码  收藏代码
  1. import com.google.common.collect.Lists;  
  2. import org.apache.commons.lang3.SerializationUtils;  
  3. import org.apache.shiro.codec.Base64;  
  4. import org.apache.shiro.session.Session;  
  5.   
  6. import java.io.Serializable;  
  7. import java.util.Collection;  
  8. import java.util.List;  
  9.   
  10. public class SerializeUtils extends SerializationUtils {  
  11.   
  12.     public static String serializeToString(Serializable obj) {  
  13.         try {  
  14.             byte[] value = serialize(obj);  
  15.             return Base64.encodeToString(value);  
  16.         } catch (Exception e) {  
  17.             throw new RuntimeException("serialize session error", e);  
  18.         }  
  19.     }  
  20.   
  21.     public static <T> T deserializeFromString(String base64) {  
  22.         try {  
  23.             byte[] objectData = Base64.decode(base64);  
  24.             return deserialize(objectData);  
  25.         } catch (Exception e) {  
  26.             throw new RuntimeException("deserialize session error", e);  
  27.         }  
  28.     }  
  29.   
  30.     public static <T> Collection<T> deserializeFromStringController(Collection<String> base64s) {  
  31.         try {  
  32.             List<T> list = Lists.newLinkedList();  
  33.             for (String base64 : base64s) {  
  34.                 byte[] objectData = Base64.decode(base64);  
  35.                 T t = deserialize(objectData);  
  36.                 list.add(t);  
  37.             }  
  38.             return list;  
  39.         } catch (Exception e) {  
  40.             throw new RuntimeException("deserialize session error", e);  
  41.         }  
  42.     }  
  43. }  

 

 

 

我的Dao实现,ShiroSession是我自己实现的,原因在后面说明,默认使用的是SimpleSession

Java代码  收藏代码
  1. import com.genertech.adp.web.common.utils.SerializeUtils;  
  2. import com.genertech.adp.web.sys.authentication.component.ShiroSession;  
  3. import com.genertech.adp.web.sys.redis.component.JedisUtils;  
  4. import org.apache.commons.lang3.StringUtils;  
  5. import org.apache.shiro.session.Session;  
  6. import org.apache.shiro.session.UnknownSessionException;  
  7. import org.apache.shiro.session.mgt.ValidatingSession;  
  8. import org.apache.shiro.session.mgt.eis.CachingSessionDAO;  
  9. import org.apache.shiro.subject.support.DefaultSubjectContext;  
  10. import org.apache.shiro.util.CollectionUtils;  
  11. import org.joda.time.DateTime;  
  12. import org.slf4j.Logger;  
  13. import org.slf4j.LoggerFactory;  
  14. import org.springframework.beans.factory.annotation.Autowired;  
  15. import redis.clients.jedis.Jedis;  
  16. import redis.clients.jedis.Transaction;  
  17.   
  18. import java.io.Serializable;  
  19. import java.util.Collection;  
  20. import java.util.List;  
  21. import java.util.Set;  
  22.   
  23. /** 
  24.  * 针对自定义的ShiroSession的Redis CRUD操作,通过isChanged标识符,确定是否需要调用Update方法 
  25.  * 通过配置securityManager在属性cacheManager查找从缓存中查找Session是否存在,如果找不到才调用下面方法 
  26.  * Shiro内部相应的组件(DefaultSecurityManager)会自动检测相应的对象(如Realm)是否实现了CacheManagerAware并自动注入相应的CacheManager。 
  27.  */  
  28. public class ShiroSessionDao extends CachingSessionDAO {  
  29.   
  30.     private static final Logger logger = LoggerFactory.getLogger(ShiroSessionDao.class);  
  31.   
  32.     // 保存到Redis中key的前缀 prefix+sessionId  
  33.     private String prefix = "";  
  34.   
  35.     // 设置会话的过期时间  
  36.     private int seconds = 0;  
  37.   
  38.     // 特殊配置 只用于没有Redis时 将Session放到EhCache中  
  39.     private Boolean onlyEhCache;  
  40.   
  41.     @Autowired  
  42.     private JedisUtils jedisUtils;  
  43.   
  44.     /** 
  45.      * 重写CachingSessionDAO中readSession方法,如果Session中没有登陆信息就调用doReadSession方法从Redis中重读 
  46.      */  
  47. //    @Override  
  48.     public Session readSession(Serializable sessionId) throws UnknownSessionException {  
  49.         Session cached = null;  
  50.         try {  
  51.             cached = super.getCachedSession(sessionId);  
  52.         } catch (Exception e) {  
  53.             e.printStackTrace();  
  54.         }  
  55.         if (onlyEhCache) {  
  56.             return cached;  
  57.         }  
  58.         // 如果缓存不存在或者缓存中没有登陆认证后记录的信息就重新从Redis中读取  
  59.         if (cached == null || cached.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY) == null) {  
  60.             try {  
  61.                 cached = this.doReadSession(sessionId);  
  62.                 if (cached == null) {  
  63.                     throw new UnknownSessionException();  
  64.                 } else {  
  65.                     // 重置Redis中缓存过期时间并缓存起来 只有设置change才能更改最后一次访问时间  
  66.                     ((ShiroSession) cached).setChanged(true);  
  67.                     super.update(cached);  
  68.                 }  
  69.             } catch (Exception e) {  
  70.                 logger.warn("There is no session with id [" + sessionId + "]");  
  71.             }  
  72.         }  
  73.         return cached;  
  74.     }  
  75.   
  76.     /** 
  77.      * 从Redis中读取Session,并重置过期时间 
  78.      * 
  79.      * @param sessionId 会话ID 
  80.      * @return ShiroSession 
  81.      */  
  82. //    @Override  
  83.     protected Session doReadSession(Serializable sessionId) {  
  84.         Session session = null;  
  85.         Jedis jedis = null;  
  86.         try {  
  87.             jedis = jedisUtils.getResource();  
  88.             String key = prefix + sessionId;  
  89.             String value = jedis.get(key);  
  90.             if (StringUtils.isNotBlank(value)) {  
  91.                 session = SerializeUtils.deserializeFromString(value);  
  92.                 logger.info("shiro session id {} 被读取", sessionId);  
  93.             }  
  94.         } catch (Exception e) {  
  95.             logger.warn("读取Session失败", e);  
  96.         } finally {  
  97.             jedisUtils.returnResource(jedis);  
  98.         }  
  99.   
  100.         return session;  
  101.     }  
  102.   
  103.   
  104.     /** 
  105.      * 从Redis中读取,但不重置Redis中缓存过期时间 
  106.      */  
  107.     public Session doReadSessionWithoutExpire(Serializable sessionId) {  
  108.         if (onlyEhCache) {  
  109.             return readSession(sessionId);  
  110.         }  
  111.   
  112.         Session session = null;  
  113.         Jedis jedis = null;  
  114.         try {  
  115.             jedis = jedisUtils.getResource();  
  116.             String key = prefix + sessionId;  
  117.             String value = jedis.get(key);  
  118.             if (StringUtils.isNotBlank(value)) {  
  119.                 session = SerializeUtils.deserializeFromString(value);  
  120.             }  
  121.         } catch (Exception e) {  
  122.             logger.warn("读取Session失败", e);  
  123.         } finally {  
  124.             jedisUtils.returnResource(jedis);  
  125.         }  
  126.   
  127.         return session;  
  128.     }  
  129.   
  130.     /** 
  131.      * 如DefaultSessionManager在创建完session后会调用该方法; 
  132.      * 如保存到关系数据库/文件系统/NoSQL数据库;即可以实现会话的持久化; 
  133.      * 返回会话ID;主要此处返回的ID.equals(session.getId()); 
  134.      */  
  135. //    @Override  
  136.     protected Serializable doCreate(Session session) {  
  137.         // 创建一个Id并设置给Session  
  138.         Serializable sessionId = this.generateSessionId(session);  
  139.         assignSessionId(session, sessionId);  
  140.         if (onlyEhCache) {  
  141.             return sessionId;  
  142.         }  
  143.         Jedis jedis = null;  
  144.         try {  
  145.             jedis = jedisUtils.getResource();  
  146.             // session由Redis缓存失效决定,这里只是简单标识  
  147.             session.setTimeout(seconds);  
  148.             jedis.setex(prefix + sessionId, seconds, SerializeUtils.serializeToString((ShiroSession) session));  
  149.             logger.info("shiro session id {} 被创建", sessionId);  
  150.         } catch (Exception e) {  
  151.             logger.warn("创建Session失败", e);  
  152.         } finally {  
  153.             jedisUtils.returnResource(jedis);  
  154.         }  
  155.         return sessionId;  
  156.     }  
  157.   
  158.     /** 
  159.      * 更新会话;如更新会话最后访问时间/停止会话/设置超时时间/设置移除属性等会调用 
  160.      */  
  161. //    @Override  
  162.     protected void doUpdate(Session session) {  
  163.         //如果会话过期/停止 没必要再更新了  
  164.         try {  
  165.             if (session instanceof ValidatingSession && !((ValidatingSession) session).isValid()) {  
  166.                 return;  
  167.             }  
  168.         } catch (Exception e) {  
  169.             logger.error("ValidatingSession error");  
  170.         }  
  171.   
  172.         if (onlyEhCache) {  
  173.             return;  
  174.         }  
  175.   
  176.         Jedis jedis = null;  
  177.         try {  
  178.             if (session instanceof ShiroSession) {  
  179.                 // 如果没有主要字段(除lastAccessTime以外其他字段)发生改变  
  180.                 ShiroSession ss = (ShiroSession) session;  
  181.                 if (!ss.isChanged()) {  
  182.                     return;  
  183.                 }  
  184.                 Transaction tx = null;  
  185.                 try {  
  186.                     jedis = jedisUtils.getResource();  
  187.                     // 开启事务  
  188.                     tx = jedis.multi();  
  189.                     ss.setChanged(false);  
  190.                     ss.setLastAccessTime(DateTime.now().toDate());  
  191.                     tx.setex(prefix + session.getId(), seconds, SerializeUtils.serializeToString(ss));  
  192.                     logger.info("shiro session id {} 被更新", session.getId(), session.getClass().getName());  
  193.                     // 执行事务  
  194.                     tx.exec();  
  195.                 } catch (Exception e) {  
  196.                     if (tx != null) {  
  197.                         // 取消执行事务  
  198.                         tx.discard();  
  199.                     }  
  200.                     throw e;  
  201.                 }  
  202.   
  203.             } else if (session instanceof Serializable) {  
  204.                 jedis = jedisUtils.getResource();  
  205.                 jedis.setex(prefix + session.getId(), seconds, SerializeUtils.serializeToString((Serializable) session));  
  206.                 logger.info("ID {} classname {} 作为非ShiroSession对象被更新, ", session.getId(), session.getClass().getName());  
  207.             } else {  
  208.                 logger.info("ID {} classname {} 不能被序列化 更新失败", session.getId(), session.getClass().getName());  
  209.             }  
  210.         } catch (Exception e) {  
  211.             logger.warn("更新Session失败", e);  
  212.         } finally {  
  213.             jedisUtils.returnResource(jedis);  
  214.         }  
  215.     }  
  216.   
  217.     /** 
  218.      * 删除会话;当会话过期/会话停止(如用户退出时)会调用 
  219.      */  
  220.     @Override  
  221.     public void doDelete(Session session) {  
  222.         Jedis jedis = null;  
  223.         try {  
  224.             jedis = jedisUtils.getResource();  
  225.             jedis.del(prefix + session.getId());  
  226.             logger.info("shiro session id {} 被删除", session.getId());  
  227.         } catch (Exception e) {  
  228.             logger.warn("删除Session失败", e);  
  229.         } finally {  
  230.             jedisUtils.returnResource(jedis);  
  231.         }  
  232.     }  
  233.   
  234.     /** 
  235.      * 删除cache中缓存的Session 
  236.      */  
  237.     public void uncache(Serializable sessionId) {  
  238.         try {  
  239.             Session session = super.getCachedSession(sessionId);  
  240.             super.uncache(session);  
  241.             logger.info("shiro session id {} 的缓存失效", sessionId);  
  242.         } catch (Exception e) {  
  243.             e.printStackTrace();  
  244.         }  
  245.     }  
  246.   
  247.     /** 
  248.      * 获取当前所有活跃用户,如果用户量多此方法影响性能 
  249.      */  
  250.     @Override  
  251.     public Collection<Session> getActiveSessions() {  
  252.         Jedis jedis = null;  
  253.         try {  
  254.             jedis = jedisUtils.getResource();  
  255.             Set<String> keys = jedis.keys(prefix + "*");  
  256.             if (CollectionUtils.isEmpty(keys)) {  
  257.                 return null;  
  258.             }  
  259.             List<String> valueList = jedis.mget(keys.toArray(new String[0]));  
  260.             return SerializeUtils.deserializeFromStringController(valueList);  
  261.         } catch (Exception e) {  
  262.             logger.warn("统计Session信息失败", e);  
  263.         } finally {  
  264.             jedisUtils.returnResource(jedis);  
  265.         }  
  266.         return null;  
  267.     }  
  268.   
  269.     /** 
  270.      * 返回本机Ehcache中Session 
  271.      */  
  272.     public Collection<Session> getEhCacheActiveSessions() {  
  273.         return super.getActiveSessions();  
  274.     }  
  275.   
  276.     public void setPrefix(String prefix) {  
  277.         this.prefix = prefix;  
  278.     }  
  279.   
  280.     public void setSeconds(int seconds) {  
  281.         this.seconds = seconds;  
  282.     }  
  283.   
  284.     public void setOnlyEhCache(Boolean onlyEhCache) {  
  285.         this.onlyEhCache = onlyEhCache;  
  286.     }  
  287. }  

 

 

 

 

二.Session和SessionFactory

步骤2:经过上面的开发已经可以使用的,但发现每次访问都会多次调用SessionDAO的doUpdate方法,来更新Redis上数据,过来发现更新的字段只有LastAccessTime(最后一次访问时间),由于会话失效是由Redis数据过期实现的,这个字段意义不大,为了减少对Redis的访问,降低网络压力,实现自己的Session,在SimpleSession上套一层,增加一个标识位,如果Session除lastAccessTime意外其它字段修改,就标识一下,只有标识为修改的才可以通过doUpdate访问Redis,否则直接返回。这也是上面SessionDao中doUpdate中逻辑判断的意义

 

Java代码  收藏代码
  1. package com.gqshao.authentication.session;  
  2.   
  3.   
  4. import org.apache.shiro.session.mgt.SimpleSession;  
  5.   
  6. import java.io.Serializable;  
  7. import java.util.Date;  
  8. import java.util.Map;  
  9.   
  10.   
  11. /** 
  12.  * 由于SimpleSession lastAccessTime更改后也会调用SessionDao update方法, 
  13.  * 增加标识位,如果只是更新lastAccessTime SessionDao update方法直接返回 
  14.  */  
  15. public class ShiroSession extends SimpleSession implements Serializable {  
  16.     // 除lastAccessTime以外其他字段发生改变时为true  
  17.     private boolean isChanged;  
  18.   
  19.     public ShiroSession() {  
  20.         super();  
  21.         this.setChanged(true);  
  22.     }  
  23.   
  24.     public ShiroSession(String host) {  
  25.         super(host);  
  26.         this.setChanged(true);  
  27.     }  
  28.   
  29.   
  30.     @Override  
  31.     public void setId(Serializable id) {  
  32.         super.setId(id);  
  33.         this.setChanged(true);  
  34.     }  
  35.   
  36.     @Override  
  37.     public void setStopTimestamp(Date stopTimestamp) {  
  38.         super.setStopTimestamp(stopTimestamp);  
  39.         this.setChanged(true);  
  40.     }  
  41.   
  42.     @Override  
  43.     public void setExpired(boolean expired) {  
  44.         super.setExpired(expired);  
  45.         this.setChanged(true);  
  46.     }  
  47.   
  48.     @Override  
  49.     public void setTimeout(long timeout) {  
  50.         super.setTimeout(timeout);  
  51.         this.setChanged(true);  
  52.     }  
  53.   
  54.     @Override  
  55.     public void setHost(String host) {  
  56.         super.setHost(host);  
  57.         this.setChanged(true);  
  58.     }  
  59.   
  60.     @Override  
  61.     public void setAttributes(Map<Object, Object> attributes) {  
  62.         super.setAttributes(attributes);  
  63.         this.setChanged(true);  
  64.     }  
  65.   
  66.     @Override  
  67.     public void setAttribute(Object key, Object value) {  
  68.         super.setAttribute(key, value);  
  69.         this.setChanged(true);  
  70.     }  
  71.   
  72.     @Override  
  73.     public Object removeAttribute(Object key) {  
  74.         this.setChanged(true);  
  75.         return super.removeAttribute(key);  
  76.     }  
  77.   
  78.     /** 
  79.      * 停止 
  80.      */  
  81.     @Override  
  82.     public void stop() {  
  83.         super.stop();  
  84.         this.setChanged(true);  
  85.     }  
  86.   
  87.     /** 
  88.      * 设置过期 
  89.      */  
  90.     @Override  
  91.     protected void expire() {  
  92.         this.stop();  
  93.         this.setExpired(true);  
  94.     }  
  95.   
  96.     public boolean isChanged() {  
  97.         return isChanged;  
  98.     }  
  99.   
  100.     public void setChanged(boolean isChanged) {  
  101.         this.isChanged = isChanged;  
  102.     }  
  103.   
  104.     @Override  
  105.     public boolean equals(Object obj) {  
  106.         return super.equals(obj);  
  107.     }  
  108.   
  109.     @Override  
  110.     protected boolean onEquals(SimpleSession ss) {  
  111.         return super.onEquals(ss);  
  112.     }  
  113.   
  114.     @Override  
  115.     public int hashCode() {  
  116.         return super.hashCode();  
  117.     }  
  118.   
  119.     @Override  
  120.     public String toString() {  
  121.         return super.toString();  
  122.     }  
  123. }  

 

 

 

Java代码  收藏代码
  1. package com.gqshao.authentication.session;  
  2.   
  3. import org.apache.commons.lang3.StringUtils;  
  4. import org.apache.shiro.session.Session;  
  5. import org.apache.shiro.session.mgt.SessionContext;  
  6. import org.apache.shiro.session.mgt.SessionFactory;  
  7. import org.apache.shiro.web.session.mgt.DefaultWebSessionContext;  
  8. import org.slf4j.Logger;  
  9. import org.slf4j.LoggerFactory;  
  10.   
  11. import javax.servlet.http.HttpServletRequest;  
  12.   
  13. public class ShiroSessionFactory implements SessionFactory {  
  14.     private static final Logger logger = LoggerFactory.getLogger(ShiroSessionFactory.class);  
  15.   
  16.     @Override  
  17.     public Session createSession(SessionContext initData) {  
  18.         ShiroSession session = new ShiroSession();  
  19.         HttpServletRequest request = (HttpServletRequest)initData.get(DefaultWebSessionContext.class.getName() + ".SERVLET_REQUEST");  
  20.         session.setHost(getIpAddress(request));  
  21.         return session;  
  22.     }  
  23.   
  24.     public static String getIpAddress(HttpServletRequest request) {  
  25.         String localIP = "127.0.0.1";  
  26.         String ip = request.getHeader("x-forwarded-for");  
  27.         if (StringUtils.isBlank(ip) || (ip.equalsIgnoreCase(localIP)) || "unknown".equalsIgnoreCase(ip)) {  
  28.             ip = request.getHeader("Proxy-Client-IP");  
  29.         }  
  30.         if (StringUtils.isBlank(ip) || (ip.equalsIgnoreCase(localIP)) || "unknown".equalsIgnoreCase(ip)) {  
  31.             ip = request.getHeader("WL-Proxy-Client-IP");  
  32.         }  
  33.         if (StringUtils.isBlank(ip) || (ip.equalsIgnoreCase(localIP)) || "unknown".equalsIgnoreCase(ip)) {  
  34.             ip = request.getRemoteAddr();  
  35.         }  
  36.         return ip;  
  37.     }  
  38. }  

 

 

三.SessionListener

步骤3:发现用户退出后,Session没有从Redis中销毁,虽然当前重新new了一个,但会对统计带来干扰,通过SessionListener解决这个问题

 

Java代码  收藏代码
  1. package com.gqshao.authentication.listener;  
  2.   
  3. import com.gqshao.authentication.dao.CachingShiroSessionDao;  
  4. import org.apache.shiro.session.Session;  
  5. import org.apache.shiro.session.SessionListener;  
  6. import org.slf4j.Logger;  
  7. import org.slf4j.LoggerFactory;  
  8. import org.springframework.beans.factory.annotation.Autowired;  
  9.   
  10. public class ShiroSessionListener implements SessionListener {  
  11.   
  12.     private static final Logger logger = LoggerFactory.getLogger(ShiroSessionListener.class);  
  13.   
  14.     @Autowired  
  15.     private CachingShiroSessionDao sessionDao;  
  16.   
  17.     @Override  
  18.     public void onStart(Session session) {  
  19.         // 会话创建时触发  
  20.         logger.info("ShiroSessionListener session {} 被创建", session.getId());  
  21.     }  
  22.   
  23.     @Override  
  24.     public void onStop(Session session) {  
  25.         sessionDao.delete(session);  
  26.         // 会话被停止时触发  
  27.         logger.info("ShiroSessionListener session {} 被销毁", session.getId());  
  28.     }  
  29.   
  30.     @Override  
  31.     public void onExpiration(Session session) {  
  32.         sessionDao.delete(session);  
  33.         //会话过期时触发  
  34.         logger.info("ShiroSessionListener session {} 过期", session.getId());  
  35.     }  
  36. }  

 

 

四.将账号信息放到Session中

修改realm中AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)方法,在返回AuthenticationInfo之前添加下面的代码,把用户信息放到Session中

 

Java代码  收藏代码
  1. // 把账号信息放到Session中,并更新缓存,用于会话管理  
  2. Subject subject = SecurityUtils.getSubject();  
  3. Serializable sessionId = subject.getSession().getId();  
  4. ShiroSession session = (ShiroSession) sessionDao.doReadSessionWithoutExpire(sessionId);  
  5. session.setAttribute("userId", su.getId());  
  6. session.setAttribute("loginName", su.getLoginName());  
  7. sessionDao.update(session);  

 

  

五. 配置文件

 

Xml代码  收藏代码
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  3.        xmlns:util="http://www.springframework.org/schema/util"  
  4.        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd  
  5.     http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd">  
  6.   
  7.   
  8.     <description>Shiro安全配置</description>  
  9.   
  10.     <!-- Shiro's main business-tier object for web-enabled applications -->  
  11.     <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
  12.         <property name="realm" ref="shiroDbRealm"/>  
  13.         <!-- 可选项 最好使用,SessionDao,中 doReadSession 读取过于频繁了-->  
  14.         <property name="cacheManager" ref="shiroEhcacheManager"/>  
  15.         <!--可选项 默认使用ServletContainerSessionManager,直接使用容器的HttpSession,可以通过配置sessionManager,使用DefaultWebSessionManager来替代-->  
  16.         <property name="sessionManager" ref="sessionManager"/>  
  17.     </bean>  
  18.   
  19.     <!-- 項目自定义的Realm -->  
  20.     <bean id="shiroDbRealm" class="com.gqshao.authentication.realm.ShiroDbRealm"/>  
  21.   
  22.     <!-- Shiro Filter -->  
  23.     <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
  24.         <property name="securityManager" ref="securityManager"/>  
  25.         <!-- 指向登陆路径,整合spring时指向控制器方法地址 -->  
  26.         <property name="loginUrl" value="/login"/>  
  27.         <property name="successUrl" value="/"/>  
  28.         <!-- 可选配置,通过实现自己的AuthenticatingFilter实现表单的自定义 -->  
  29.         <property name="filters">  
  30.             <util:map>  
  31.                 <entry key="authc">  
  32.                     <bean class="com.gqshao.authentication.filter.MyAuthenticationFilter"/>  
  33.                 </entry>  
  34.             </util:map>  
  35.         </property>  
  36.   
  37.         <property name="filterChainDefinitions">  
  38.             <value>  
  39.                 /login = authc  
  40.                 /logoutlogout = logout  
  41.                 /static/** = anon  
  42.                 /** = user  
  43.             </value>  
  44.         </property>  
  45.     </bean>  
  46.   
  47.     <!-- 用户授权信息Cache, 采用EhCache,本地缓存最长时间应比中央缓存时间短一些,以确保Session中doReadSession方法调用时更新中央缓存过期时间 -->  
  48.     <bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">  
  49.         <property name="cacheManagerConfigFile" value="classpath:security/ehcache-shiro.xml"/>  
  50.     </bean>  
  51.   
  52.     <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">  
  53.         <!-- 设置全局会话超时时间,默认30分钟(1800000) -->  
  54.         <property name="globalSessionTimeout" value="1800000"/>  
  55.         <!-- 是否在会话过期后会调用SessionDAO的delete方法删除会话 默认true-->  
  56.         <property name="deleteInvalidSessions" value="false"/>  
  57.         <!-- 是否开启会话验证器任务 默认true -->  
  58.         <property name="sessionValidationSchedulerEnabled" value="false"/>  
  59.         <!-- 会话验证器调度时间 -->  
  60.         <property name="sessionValidationInterval" value="1800000"/>  
  61.         <property name="sessionFactory" ref="sessionFactory"/>  
  62.         <property name="sessionDAO" ref="sessionDao"/>  
  63.         <!-- 默认JSESSIONID,同tomcat/jetty在cookie中缓存标识相同,修改用于防止访问404页面时,容器生成的标识把shiro的覆盖掉 -->  
  64.         <property name="sessionIdCookie">  
  65.             <bean class="org.apache.shiro.web.servlet.SimpleCookie">  
  66.                 <constructor-arg name="name" value="SHRIOSESSIONID"/>  
  67.             </bean>  
  68.         </property>  
  69.         <property name="sessionListeners">  
  70.             <list>  
  71.                 <bean class="com.gqshao.authentication.listener.ShiroSessionListener"/>  
  72.             </list>  
  73.         </property>  
  74.     </bean>  
  75.   
  76.     <!-- 自定义Session工厂方法 返回会标识是否修改主要字段的自定义Session-->  
  77.     <bean id="sessionFactory" class="com.gqshao.authentication.session.ShiroSessionFactory"/>  
  78.   
  79.     <!-- 普通持久化接口,不会被缓存 每次doReadSession会被反复调用 -->  
  80.     <!--<bean class="com.gqshao.authentication.dao.RedisSessionDao">-->  
  81.     <!-- 使用可被缓存的Dao ,本地缓存减轻网络压力 -->  
  82.     <!--<bean id="sessionDao" class="com.gqshao.authentication.dao.CachingSessionDao">-->  
  83.     <!-- 可缓存Dao,操作自定义Session,添加标识位,减少doUpdate方法中Redis的连接次数来减轻网络压力 -->  
  84.     <bean id="sessionDao" class="com.gqshao.authentication.dao.CachingShiroSessionDao">  
  85.         <property name="prefix" value="ShiroSession_"/>  
  86.         <!-- 注意中央缓存有效时间要比本地缓存有效时间长-->  
  87.         <property name="seconds" value="1800"/>  
  88.         <!-- 特殊配置 只用于没有Redis时 将Session放到EhCache中 -->  
  89.         <property name="onlyEhCache" value="false"/>  
  90.     </bean>  
  91.   
  92.   
  93.     <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->  
  94.     <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>  
  95.   
  96.     <!-- AOP式方法级权限检查 -->  
  97.     <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"  
  98.           depends-on="lifecycleBeanPostProcessor">  
  99.         <property name="proxyTargetClass" value="true"/>  
  100.     </bean>  
  101.     <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">  
  102.         <property name="securityManager" ref="securityManager"/>  
  103.     </bean>  
  104. </beans>  

 

Xml代码  收藏代码
  1. <ehcache updateCheck="false" name="shiroCache">  
  2.     <!--  
  3.         timeToIdleSeconds 当缓存闲置n秒后销毁 为了保障会调用ShiroSessionDao的doReadSession方法,所以不配置该属性  
  4.         timeToLiveSeconds 当缓存存活n秒后销毁 必须比Redis中过期时间短  
  5.     -->  
  6.     <defaultCache  
  7.             maxElementsInMemory="10000"  
  8.             eternal="false"  
  9.             timeToLiveSeconds="60"  
  10.             overflowToDisk="false"  
  11.             diskPersistent="false"  
  12.             diskExpiryThreadIntervalSeconds="10"  
  13.             />  
  14. </ehcache>  

 

 

六.测试会话管理

Java代码  收藏代码
  1. package com.gqshao.authentication.controller;  
  2.   
  3. import com.gqshao.authentication.dao.CachingShiroSessionDao;  
  4. import org.apache.shiro.session.Session;  
  5. import org.springframework.beans.factory.annotation.Autowired;  
  6. import org.springframework.stereotype.Controller;  
  7. import org.springframework.web.bind.annotation.RequestMapping;  
  8. import org.springframework.web.bind.annotation.ResponseBody;  
  9.   
  10. import java.io.Serializable;  
  11. import java.util.Collection;  
  12.   
  13. @Controller  
  14. @RequestMapping("/session")  
  15. public class SessionController {  
  16.   
  17.     @Autowired  
  18.     private CachingShiroSessionDao sessionDao;  
  19.   
  20.     @RequestMapping("/active")  
  21.     @ResponseBody  
  22.     public Collection<Session> getActiveSessions() {  
  23.         return sessionDao.getActiveSessions();  
  24.     }  
  25.   
  26.     @RequestMapping("/read")  
  27.     @ResponseBody  
  28.     public Session readSession(Serializable sessionId) {  
  29.         return sessionDao.doReadSessionWithoutExpire(sessionId);  
  30.     }  
  31. }  

 

 

 七.集群情况下的改造

1.问题上面启用了Redis中央缓存、EhCache本地JVM缓存,AuthorizingRealm的doGetAuthenticationInfo登陆认证方法返回的AuthenticationInfo,默认情况下会被保存到Session的Attribute下面两个字段中

 

Java代码  收藏代码
  1. org.apache.shiro.subject.support.DefaultSubjectContext.PRINCIPALS_SESSION_KEY 保存 principal  
  2. org.apache.shiro.subject.support.DefaultSubjectContext.AUTHENTICATED_SESSION_KEY 保存 boolean是否登陆  

 

然后在每次请求过程中,在ShiroFilter中组装Subject时,读取Session中这两个字段

现在的问题是Session被缓存到本地JVM堆中,也就是说服务器A登陆,无法修改服务器B的EhCache中Session属性,导致服务器B没有登陆。

处理方法有很多思路,比如重写CachingSessionDAO,readSession如果没有这两个属性就不缓存(没登陆就不缓存),或者cache的session没有这两个属性就调用自己实现的doReadSession方法从Redis中重读一下。

 

2.readSession中每次调用doReadSession方法的时候,都代表第一次读取,或本地EhCache失效,我们可以在这个时候调用一下updateSession方法,重新设置一下最后一次访问时间,当然要把isChange设置为true才会保存到Redis中。

 

3.如果需要保持各个服务器Session是完全同步的,可以通过Redis消息订阅/发布功能,订阅一份消息,当得到消息后,可以调用SessionDao中已经实现了删除Session本地缓存的方法

 

对比:

https://yuhuiblog695685688425687986842568269.iteye.com/blog/2373145

cas自己的session没有实现序列化,需要辅助以tomcat的session

分享到:
评论

相关推荐

    shiro连接redis集群 根据org.crazycake.shiro包改造源码

    Apache Shiro是一个强大的Java安全框架,它提供了身份验证、授权、会话管理和加密等功能。在分布式系统中,尤其是在使用Redis作为缓存或session存储时,Shiro的原生支持可能无法直接与Redis集群配合工作。这里提到的...

    springboot+shiro+redis整合

    通过以上步骤,我们成功地将SpringBoot、Shiro和Redis整合在一起,实现了基于Shiro的安全控制和Redis的Session共享。这种架构方案适用于大型分布式系统,能够提供稳定、高效且安全的用户体验。在实际应用中,还可以...

    SSM+shiro+redis

    在SSM+Shiro架构中,Redis可以用来存储用户的Session信息,实现分布式会话管理,尤其在集群环境下,确保用户在不同服务器之间切换时仍能保持登录状态。此外,Redis还可以用于缓存Shiro的权限信息,提高权限查询速度...

    shiro-redis-cluster

    1. 集成Shiro的Session管理与Redis集群,需要正确配置集群节点信息,并处理好会话ID的分配与一致性。 2. 在Spring MVC中,合理配置Shiro Filter,避免与Spring Security冲突。 3. 在Redis集群中,注意解决可能出现的...

    shiro-redisson基于Redis的ShiroCache和Session实现

    `shiro-redisson` 提供的基于 Redis 的会话管理,可以将会话数据存储在 Redis 中,实现会话在集群中的共享,确保用户在任何节点上都能保持会话状态。 4. **配置与集成** 要使用 `shiro-redisson`,首先需要在 ...

    springboot-shiro-redis

    集成Redis后,Shiro将Session持久化到Redis,实现集群环境下的Session共享。 7. **DAO层自动生成插件**: - 这个项目集成了一个DAO层代码自动生成工具,可以帮助开发者快速生成与数据库交互的代码,提高开发效率。...

    Shiro集成 Redis 方案

    为了解决这个问题,我们可以将 Shiro 集成 Redis,利用 Redis 的分布式特性和高性能来实现集中式的缓存管理。 Redis 是一款开源的内存数据结构存储系统,可以当作数据库、缓存和消息代理来使用。它的数据模型多样,...

    shiro-redis-session-master.zip

    在这个名为"shiro-redis-session-master"的项目中,开发者采用Redis作为Shiro的会话管理存储,以实现跨服务器的集群会话共享,从而提高系统的可扩展性和可用性。 **一、Shiro框架** Shiro框架的核心组件包括...

    ssm集成redis和shiro

    1. **会话管理**: 通过Shiro的SessionManager,配置为使用Redis作为session的存储,实现分布式会话,解决集群环境下的session共享问题。 2. **权限缓存**: 将Shiro的权限信息缓存在Redis中,提高权限验证效率,同时...

    shrio redis实现集群模式下的session共享

    Shiro与Redis结合,可以通过自定义SessionDAO来实现在集群环境下的Session共享。首先,我们需要引入Shiro和Redis的相关依赖,包括Shiro的core库以及连接Redis的客户端如Jedis或Lettuce。接下来,我们需要创建一个...

    iherus-shiro-redis-master.zip_iherus_redis

    而Shiro则通过其自身的Session超时管理机制,配合Redis的过期策略,实现自动清理无效会话,防止会话固定攻击。 在"shiro-redis"这个子目录下,我们可以找到该项目关于Shiro与Redis整合的具体实现代码,包括配置文件...

    shiro+redis.zip

    通过以上步骤,Shiro 就能利用 Redis 进行分布式会话管理,从而提高系统的可扩展性和可靠性。这种结合方式对于大型分布式应用来说非常重要,因为它能够保证用户在不同服务器间的会话一致性,提高用户体验,同时减轻...

    shiro-demo使用redis做缓存.zip

    Apache Shiro是一个强大的Java安全框架,它提供了身份验证、授权、会话管理和加密等功能,广泛应用于Web应用和企业级系统中。在这个"shiro-demo使用redis做缓存"的示例中,我们将深入探讨如何利用Redis这个高性能的...

    shiro-redis-master.zip

    Apache Shiro是一个强大的Java安全框架,它提供了身份验证、授权、加密和会话管理功能,使得开发者能够轻松处理应用程序的安全需求。"shiro-redis-master.zip" 是一个针对Shiro框架的扩展,它将默认的Ehcache缓存...

    基于ssm+shiro+redis+nginx tomcat服务器集群管理项目源码+项目说明.zip

    redis:Nosql数据库,搭配shiro的会话管理功能将session存入redis中,实现tomcat多服务器集群的session共享 nginx:反向代理服务器,用来调度多台tomcat h2:内存数据库,用于测试 开发环境 ==== jdk...

    spring +shiro+redis

    通过Shiro,开发者可以轻松实现用户身份验证、权限控制以及会话管理,为应用程序添加安全层。 **Redis** 是一个高性能的键值存储系统,通常用作数据库、缓存和消息代理。它的特点是数据持久化、支持多种数据结构...

    基于ssm+shiro+redis+nginx tomcat服务器集群管理项目.zip

    该项目是一个综合性的Web应用,基于Java的SSM(Spring、SpringMVC、MyBatis)框架,结合Apache Shiro进行权限管理和Redis缓存技术,以及Nginx作为反向代理和负载均衡器,用于实现Tomcat服务器集群的高效管理。...

    shiro redis session共享

    标题“shiro redis session共享”涉及的是Web应用中关于用户会话管理的技术,主要集中在Apache Shiro框架和Redis缓存系统上。Apache Shiro是一款强大的Java安全框架,它提供了身份验证、授权、加密和会话管理功能。...

    spring boot整合redis实现shiro的分布式session共享的方法

    Shiro 通过 SessionManager 来管理 Session,而 Session 的操作则是通过 SessionDao 来实现的。默认情况下,Shiro 实现了两种 SessionDao,分别为 CachingSessionDAO 和 MemorySessionDAO。当我们使用 EhCache 缓存...

Global site tag (gtag.js) - Google Analytics