前言

  开心一刻

    老师对小明说:"乳就是小的意思,比如乳猪就是小猪,乳名就是小名,请你用乳字造个句"
    小明:"我家很穷,只能住在40平米的乳房"
    老师:"..., 这个不行,换一个"
    小明:"我每天上学都要跳过我家门口的一条乳沟"
    老师:"......, 这个也不行,再换一个"
    小明:"老师,我想不出来了,把我的乳头都想破了!"

  路漫漫其修远兮,吾将上下而求索!

  github:https://github.com/youzhibing

  码云(gitee):https://gitee.com/youzhibing

前情回顾

  shiro的session创建session的查询、更新、过期、删除中,shiro对session的操作基本都讲到了,但还缺一个session共享没有讲解;session共享的原理其实在自定义session管理一文已经讲过了,本文不讲原理,只看看shiro的session共享的实现。

  为何需要session共享

    如果是单机应用,那么谈不上session共享,session放哪都无所谓,不在乎放到默认的servlet容器中,还是抽出来放到单独的地方;

    也就是说session共享是针对集群(或分布式、或分布式集群)的;如果不做session共享,仍然采用默认的方式(session存放到默认的servlet容器),当我们的应用是以集群的方式发布的时候,同个用户的请求会被分发到不同的集群节点(分发依赖具体的负载均衡规则),那么每个处理同个用户请求的节点都会重新生成该用户的session,这些session之间是毫无关联的。那么同个用户的请求会被当成多个不同用户的请求,这肯定是不行的。

  如何实现session共享

    实现方式其实有很多,甚至可以不做session共享,具体有哪些,大家自行去查资料。本文提供一种方式:redis实现session共享,就是将session从servlet容器抽出来,放到redis中存储,所有集群节点都从redis中对session进行操作。

SessionDAO

  SessionDAO其实是用于session持久化的,但里面有缓存部分,具体细节我们往下看

  shiro已有SessionDAO的实现如下

  SessionDAO接口提供的方法如下

  1. package org.apache.shiro.session.mgt.eis;
  2.  
  3. import org.apache.shiro.session.Session;
  4. import org.apache.shiro.session.UnknownSessionException;
  5.  
  6. import java.io.Serializable;
  7. import java.util.Collection;
  8.  
  9. /**
  10. * 从EIS操作session的规范(EIS:例如关系型数据库, 文件系统, 持久化缓存等等, 具体依赖DAO实现)
  11. * 提供了典型的CRUD的方法:create, readSession, update, delete
  12. */
  13. public interface SessionDAO {
  14.  
  15. /**
  16. * 插入一个新的sesion记录到EIS
  17. */
  18. Serializable create(Session session);
  19.  
  20. /**
  21. * 根据会话ID获取会话
  22. */
  23. Session readSession(Serializable sessionId) throws UnknownSessionException;
  24.  
  25. /**
  26. * 更新session; 如更新session最后访问时间/停止会话/设置超时时间/设置移除属性等会调用
  27. */
  28. void update(Session session) throws UnknownSessionException;
  29.  
  30. /**
  31. * 删除session; 当会话过期/会话停止(如用户退出时)会调用
  32. */
  33. void delete(Session session);
  34.  
  35. /**
  36. * 获取当前所有活跃session, 所有状态不是stopped/expired的session
  37. * 如果用户量多此方法影响性能
  38. */
  39. Collection<Session> getActiveSessions();
  40. }

    SessionDAO给出了从持久层(一般而言是关系型数据库)操作session的标准。

  AbstractSessionDAO提供了SessionDAO的基本实现,如下

  1. package org.apache.shiro.session.mgt.eis;
  2.  
  3. import org.apache.shiro.session.Session;
  4. import org.apache.shiro.session.UnknownSessionException;
  5. import org.apache.shiro.session.mgt.SimpleSession;
  6.  
  7. import java.io.Serializable;
  8.  
  9. /**
  10. * SessionDAO的抽象实现, 在会话创建和读取时做一些健全性检查,并在需要时允许可插入的会话ID生成策略.
  11. * SessionDAO的update和delete则留给子类来实现
  12. * EIS需要子类自己实现
  13. */
  14. public abstract class AbstractSessionDAO implements SessionDAO {
  15.  
  16. /**
  17. * sessionId生成器
  18. */
  19. private SessionIdGenerator sessionIdGenerator;
  20.  
  21. public AbstractSessionDAO() {
  22. this.sessionIdGenerator = new JavaUuidSessionIdGenerator(); // 指定JavaUuidSessionIdGenerator为默认sessionId生成器
  23. }
  24.  
  25. /**
  26. * 获取sessionId生成器
  27. */
  28. public SessionIdGenerator getSessionIdGenerator() {
  29. return sessionIdGenerator;
  30. }
  31.  
  32. /**
  33. * 设置sessionId生成器
  34. */
  35. public void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) {
  36. this.sessionIdGenerator = sessionIdGenerator;
  37. }
  38.  
  39. /**
  40. * 生成一个新的sessionId, 并将它应用到session实例
  41. */
  42. protected Serializable generateSessionId(Session session) {
  43. if (this.sessionIdGenerator == null) {
  44. String msg = "sessionIdGenerator attribute has not been configured.";
  45. throw new IllegalStateException(msg);
  46. }
  47. return this.sessionIdGenerator.generateId(session);
  48. }
  49.  
  50. /**
  51. * SessionDAO中create实现; 将创建的sesion保存到EIS.
  52. * 子类doCreate方法的代理,具体的细节委托给了子类的doCreate方法
  53. */
  54. public Serializable create(Session session) {
  55. Serializable sessionId = doCreate(session);
  56. verifySessionId(sessionId);
  57. return sessionId;
  58. }
  59.  
  60. /**
  61. * 保证从doCreate返回的sessionId不是null,并且不是已经存在的.
  62. * 目前只实现了null校验,是否已存在是没有校验的,可能shiro的开发者会在后续补上吧.
  63. */
  64. private void verifySessionId(Serializable sessionId) {
  65. if (sessionId == null) {
  66. String msg = "sessionId returned from doCreate implementation is null. Please verify the implementation.";
  67. throw new IllegalStateException(msg);
  68. }
  69. }
  70.  
  71. /**
  72. * 分配sessionId给session实例
  73. */
  74. protected void assignSessionId(Session session, Serializable sessionId) {
  75. ((SimpleSession) session).setId(sessionId);
  76. }
  77.  
  78. /**
  79. * 子类通过实现此方法来持久化Session实例到EIS.
  80. */
  81. protected abstract Serializable doCreate(Session session);
  82.  
  83. /**
  84. * SessionDAO中readSession实现; 通过sessionId从EIS获取session对象.
  85. * 子类doReadSession方法的代理,具体的获取细节委托给了子类的doReadSession方法.
  86. */
  87. public Session readSession(Serializable sessionId) throws UnknownSessionException {
  88. Session s = doReadSession(sessionId);
  89. if (s == null) {
  90. throw new UnknownSessionException("There is no session with id [" + sessionId + "]");
  91. }
  92. return s;
  93. }
  94.  
  95. /**
  96. * 子类通过实现此方法从EIS获取session实例
  97. */
  98. protected abstract Session doReadSession(Serializable sessionId);
  99.  
  100. }

    SessionDao的基本实现,实现了SessionDao的create、readSession(具体还是依赖AbstractSessionDAO子类的doCreate、doReadSession实现);同时加入了自己的sessionId生成器,负责sessionId的操作。

  CachingSessionDAO提供了session缓存的功能,如下

  1. package org.apache.shiro.session.mgt.eis;
  2.  
  3. import org.apache.shiro.cache.Cache;
  4. import org.apache.shiro.cache.CacheManager;
  5. import org.apache.shiro.cache.CacheManagerAware;
  6. import org.apache.shiro.session.Session;
  7. import org.apache.shiro.session.UnknownSessionException;
  8. import org.apache.shiro.session.mgt.ValidatingSession;
  9.  
  10. import java.io.Serializable;
  11. import java.util.Collection;
  12. import java.util.Collections;
  13.  
  14. /**
  15. * 应用层与持久层(EIS,如关系型数据库、文件系统、NOSQL)之间的缓存层实现
  16. * 缓存着所有激活状态的session
  17. * 实现了CacheManagerAware,会在shiro加载的过程中调用此对象的setCacheManager方法
  18. */
  19. public abstract class CachingSessionDAO extends AbstractSessionDAO implements CacheManagerAware {
  20.  
  21. /**
  22. * 激活状态的sesion的默认缓存名
  23. */
  24. public static final String ACTIVE_SESSION_CACHE_NAME = "shiro-activeSessionCache";
  25.  
  26. /**
  27. * 缓存管理器,用来获取session缓存
  28. */
  29. private CacheManager cacheManager;
  30.  
  31. /**
  32. * 用来缓存session的缓存实例
  33. */
  34. private Cache<Serializable, Session> activeSessions;
  35.  
  36. /**
  37. * session缓存名, 默认是ACTIVE_SESSION_CACHE_NAME.
  38. */
  39. private String activeSessionsCacheName = ACTIVE_SESSION_CACHE_NAME;
  40.  
  41. public CachingSessionDAO() {
  42. }
  43.  
  44. /**
  45. * 设置缓存管理器
  46. */
  47. public void setCacheManager(CacheManager cacheManager) {
  48. this.cacheManager = cacheManager;
  49. }
  50.  
  51. /**
  52. * 获取缓存管理器
  53. */
  54. public CacheManager getCacheManager() {
  55. return cacheManager;
  56. }
  57.  
  58. /**
  59. * 获取缓存实例的名称,也就是获取activeSessionsCacheName的值
  60. */
  61. public String getActiveSessionsCacheName() {
  62. return activeSessionsCacheName;
  63. }
  64.  
  65. /**
  66. * 设置缓存实例的名称,也就是设置activeSessionsCacheName的值
  67. */
  68. public void setActiveSessionsCacheName(String activeSessionsCacheName) {
  69. this.activeSessionsCacheName = activeSessionsCacheName;
  70. }
  71.  
  72. /**
  73. * 获取缓存实例
  74. */
  75. public Cache<Serializable, Session> getActiveSessionsCache() {
  76. return this.activeSessions;
  77. }
  78.  
  79. /**
  80. * 设置缓存实例
  81. */
  82. public void setActiveSessionsCache(Cache<Serializable, Session> cache) {
  83. this.activeSessions = cache;
  84. }
  85.  
  86. /**
  87. * 获取缓存实例
  88. * 注意:不会返回non-null值
  89. *
  90. * @return the active sessions cache instance.
  91. */
  92. private Cache<Serializable, Session> getActiveSessionsCacheLazy() {
  93. if (this.activeSessions == null) {
  94. this.activeSessions = createActiveSessionsCache();
  95. }
  96. return activeSessions;
  97. }
  98.  
  99. /**
  100. * 创建缓存实例
  101. */
  102. protected Cache<Serializable, Session> createActiveSessionsCache() {
  103. Cache<Serializable, Session> cache = null;
  104. CacheManager mgr = getCacheManager();
  105. if (mgr != null) {
  106. String name = getActiveSessionsCacheName();
  107. cache = mgr.getCache(name);
  108. }
  109. return cache;
  110. }
  111.  
  112. /**
  113. * AbstractSessionDAO中create的重写
  114. * 调用父类(AbstractSessionDAO)的create方法, 然后将session缓存起来
  115. * 返回sessionId
  116. */
  117. public Serializable create(Session session) {
  118. Serializable sessionId = super.create(session); // 调用父类的create方法
  119. cache(session, sessionId); // 以sessionId作为key缓存session
  120. return sessionId;
  121. }
  122.  
  123. /**
  124. * 从缓存中获取session; 若sessionId为null,则返回null
  125. */
  126. protected Session getCachedSession(Serializable sessionId) {
  127. Session cached = null;
  128. if (sessionId != null) {
  129. Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
  130. if (cache != null) {
  131. cached = getCachedSession(sessionId, cache);
  132. }
  133. }
  134. return cached;
  135. }
  136.  
  137. /**
  138. * 从缓存中获取session
  139. */
  140. protected Session getCachedSession(Serializable sessionId, Cache<Serializable, Session> cache) {
  141. return cache.get(sessionId);
  142. }
  143.  
  144. /**
  145. * 缓存session,以sessionId作为key
  146. */
  147. protected void cache(Session session, Serializable sessionId) {
  148. if (session == null || sessionId == null) {
  149. return;
  150. }
  151. Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
  152. if (cache == null) {
  153. return;
  154. }
  155. cache(session, sessionId, cache);
  156. }
  157.  
  158. protected void cache(Session session, Serializable sessionId, Cache<Serializable, Session> cache) {
  159. cache.put(sessionId, session);
  160. }
  161.  
  162. /**
  163. * AbstractSessionDAO中readSession的重写
  164. * 先从缓存中获取,若没有则调用父类的readSession方法获取session
  165. */
  166. public Session readSession(Serializable sessionId) throws UnknownSessionException {
  167. Session s = getCachedSession(sessionId); // 从缓存中获取
  168. if (s == null) {
  169. s = super.readSession(sessionId); // 调用父类readSession方法获取
  170. }
  171. return s;
  172. }
  173.  
  174. /**
  175. * SessionDAO中update的实现
  176. * 更新session的状态
  177. */
  178. public void update(Session session) throws UnknownSessionException {
  179. doUpdate(session); // 更新EIS中的session
  180. if (session instanceof ValidatingSession) {
  181. if (((ValidatingSession) session).isValid()) {
  182. cache(session, session.getId()); // 更新缓存中的session
  183. } else {
  184. uncache(session); // 移除缓存中的sesson
  185. }
  186. } else {
  187. cache(session, session.getId());
  188. }
  189. }
  190.  
  191. /**
  192. * 由子类去实现,持久化session到EIS
  193. */
  194. protected abstract void doUpdate(Session session);
  195.  
  196. /**
  197. * SessionDAO中delete的实现
  198. * 删除session
  199. */
  200. public void delete(Session session) {
  201. uncache(session); // 从缓存中移除
  202. doDelete(session); // 从EIS中删除
  203. }
  204.  
  205. /**
  206. * 由子类去实现,从EIS中删除session
  207. */
  208. protected abstract void doDelete(Session session);
  209.  
  210. /**
  211. * 从缓存中移除指定的session
  212. */
  213. protected void uncache(Session session) {
  214. if (session == null) {
  215. return;
  216. }
  217. Serializable id = session.getId();
  218. if (id == null) {
  219. return;
  220. }
  221. Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
  222. if (cache != null) {
  223. cache.remove(id);
  224. }
  225. }
  226.  
  227. /**
  228. * SessionDAO中getActiveSessions的实现
  229. * 获取所有的存活的session
  230. */
  231. public Collection<Session> getActiveSessions() {
  232. Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
  233. if (cache != null) {
  234. return cache.values();
  235. } else {
  236. return Collections.emptySet();
  237. }
  238. }
  239. }

    是应用层与持久化层之间的缓存层,不用频繁请求持久化层以提升效率。重写了AbstractSessionDAO中的create、readSession方法,实现了SessionDAO中的update、delete、getActiveSessions方法,预留doUpdate和doDelele给子类去实现(doXXX方法操作的是持久层)

  MemorySessionDAO,SessionDAO的简单内存实现,如下

  1. package org.apache.shiro.session.mgt.eis;
  2.  
  3. import org.apache.shiro.session.Session;
  4. import org.apache.shiro.session.UnknownSessionException;
  5. import org.apache.shiro.util.CollectionUtils;
  6. import org.slf4j.Logger;
  7. import org.slf4j.LoggerFactory;
  8.  
  9. import java.io.Serializable;
  10. import java.util.Collection;
  11. import java.util.Collections;
  12. import java.util.concurrent.ConcurrentHashMap;
  13. import java.util.concurrent.ConcurrentMap;
  14.  
  15. /**
  16. * 基于内存的SessionDao的简单实现,所有的session存在ConcurrentMap中
  17. * DefaultSessionManager默认用的MemorySessionDAO
  18. */
  19. public class MemorySessionDAO extends AbstractSessionDAO {
  20.  
  21. private static final Logger log = LoggerFactory.getLogger(MemorySessionDAO.class);
  22.  
  23. private ConcurrentMap<Serializable, Session> sessions; // 存放session的容器
  24.  
  25. public MemorySessionDAO() {
  26. this.sessions = new ConcurrentHashMap<Serializable, Session>();
  27. }
  28.  
  29. // AbstractSessionDAO 中doCreate的重写; 将session存入sessions
  30. protected Serializable doCreate(Session session) {
  31. Serializable sessionId = generateSessionId(session); // 生成sessionId
  32. assignSessionId(session, sessionId); // 将sessionId赋值到session
  33. storeSession(sessionId, session); // 存储session到sessions
  34. return sessionId;
  35. }
  36.  
  37. // 存储session到sessions
  38. protected Session storeSession(Serializable id, Session session) {
  39. if (id == null) {
  40. throw new NullPointerException("id argument cannot be null.");
  41. }
  42. return sessions.putIfAbsent(id, session);
  43. }
  44.  
  45. // AbstractSessionDAO 中doReadSession的重写; 从sessions中获取session
  46. protected Session doReadSession(Serializable sessionId) {
  47. return sessions.get(sessionId);
  48. }
  49.  
  50. // SessionDAO中update的实现; 更新sessions中指定的session
  51. public void update(Session session) throws UnknownSessionException {
  52. storeSession(session.getId(), session);
  53. }
  54.  
  55. // SessionDAO中delete的实现; 从sessions中移除指定的session
  56. public void delete(Session session) {
  57. if (session == null) {
  58. throw new NullPointerException("session argument cannot be null.");
  59. }
  60. Serializable id = session.getId();
  61. if (id != null) {
  62. sessions.remove(id);
  63. }
  64. }
  65.  
  66. // SessionDAO中SessionDAO中delete的实现的实现; 获取sessions中全部session
  67. public Collection<Session> SessionDAOdelete的实现() {
  68. Collection<Session> values = sessions.values();
  69. if (CollectionUtils.isEmpty(values)) {
  70. return Collections.emptySet();
  71. } else {
  72. return Collections.unmodifiableCollection(values);
  73. }
  74. }
  75.  
  76. }

    将session保存在内存中,存储结构是ConcurrentHashMap;项目中基本不用,即使我们不实现自己的SessionDAO,一般用的也是EnterpriseCacheSessionDAO。

  EnterpriseCacheSessionDAO,提供了缓存功能的session维护,如下

  1. package org.apache.shiro.session.mgt.eis;
  2.  
  3. import org.apache.shiro.cache.AbstractCacheManager;
  4. import org.apache.shiro.cache.Cache;
  5. import org.apache.shiro.cache.CacheException;
  6. import org.apache.shiro.cache.MapCache;
  7. import org.apache.shiro.session.Session;
  8.  
  9. import java.io.Serializable;
  10. import java.util.concurrent.ConcurrentHashMap;
  11.  
  12. public class EnterpriseCacheSessionDAO extends CachingSessionDAO {
  13.  
  14. public EnterpriseCacheSessionDAO() {
  15.  
  16. // 设置默认缓存器,并实例化MapCache作为cache实例
  17. setCacheManager(new AbstractCacheManager() {
  18. @Override
  19. protected Cache<Serializable, Session> createCache(String name) throws CacheException {
  20. return new MapCache<Serializable, Session>(name, new ConcurrentHashMap<Serializable, Session>());
  21. }
  22. });
  23. }
  24.  
  25. // AbstractSessionDAO中doCreate的重写;
  26. protected Serializable doCreate(Session session) {
  27. Serializable sessionId = generateSessionId(session);
  28. assignSessionId(session, sessionId);
  29. return sessionId;
  30. }
  31.  
  32. // AbstractSessionDAO中doReadSession的重写
  33. protected Session doReadSession(Serializable sessionId) {
  34. return null; //should never execute because this implementation relies on parent class to access cache, which
  35. //is where all sessions reside - it is the cache implementation that determines if the
  36. //cache is memory only or disk-persistent, etc.
  37. }
  38.  
  39. // CachingSessionDAO中doUpdate的重写
  40. protected void doUpdate(Session session) {
  41. //does nothing - parent class persists to cache.
  42. }
  43.  
  44. // CachingSessionDAO中doDelete的重写
  45. protected void doDelete(Session session) {
  46. //does nothing - parent class removes from cache.
  47. }
  48. }

    设置了默认的缓存管理器(AbstractCacheManager)和默认的缓存实例(MapCache),实现了缓存效果。从父类继承的持久化操作方法(doXXX)都是空实现,也就说EnterpriseCacheSessionDAO是没有实现持久化操作的,仅仅只是简单的提供了缓存实现。当然我们可以继承EnterpriseCacheSessionDAO,重写doXXX方法来实现持久化操作。

  总结下:SessionDAO定义了从持久层操作session的标准;AbstractSessionDAO提供了SessionDAO的基础实现,如生成会话ID等;CachingSessionDAO提供了对开发者透明的session缓存的功能,只需要设置相应的 CacheManager 即可;MemorySessionDAO直接在内存中进行session维护;而EnterpriseCacheSessionDAO提供了缓存功能的session维护,默认情况下使用 MapCache 实现,内部使用ConcurrentHashMap保存缓存的会话。因为shiro不知道我们需要将session持久化到哪里(关系型数据库,还是文件系统),所以只提供了MemorySessionDAO持久化到内存(听起来怪怪的,内存中能说成持久层吗)

shiro session共享

  共享实现

    shiro的session共享其实是比较简单的,重写CacheManager,将其操作指向我们的redis,然后实现我们自己的CachingSessionDAO定制缓存操作和缓存持久化。

    自定义CacheManager

      ShiroRedisCacheManager

  1. package com.lee.shiro.config;
  2.  
  3. import org.apache.shiro.cache.Cache;
  4. import org.apache.shiro.cache.CacheException;
  5. import org.apache.shiro.cache.CacheManager;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.stereotype.Component;
  8.  
  9. @Component
  10. public class ShiroRedisCacheManager implements CacheManager {
  11.  
  12. @Autowired
  13. private Cache shiroRedisCache;
  14.  
  15. @Override
  16. public <K, V> Cache<K, V> getCache(String s) throws CacheException {
  17. return shiroRedisCache;
  18. }
  19. }

      ShiroRedisCache

  1. package com.lee.shiro.config;
  2.  
  3. import org.apache.shiro.cache.Cache;
  4. import org.apache.shiro.cache.CacheException;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.beans.factory.annotation.Value;
  7. import org.springframework.data.redis.core.RedisTemplate;
  8. import org.springframework.stereotype.Component;
  9.  
  10. import java.util.Collection;
  11. import java.util.Set;
  12. import java.util.concurrent.TimeUnit;
  13.  
  14. @Component
  15. public class ShiroRedisCache<K,V> implements Cache<K,V>{
  16.  
  17. @Autowired
  18. private RedisTemplate<K,V> redisTemplate;
  19.  
  20. @Value("${spring.redis.expireTime}")
  21. private long expireTime;
  22.  
  23. @Override
  24. public V get(K k) throws CacheException {
  25. return redisTemplate.opsForValue().get(k);
  26. }
  27.  
  28. @Override
  29. public V put(K k, V v) throws CacheException {
  30. redisTemplate.opsForValue().set(k,v,expireTime, TimeUnit.SECONDS);
  31. return null;
  32. }
  33.  
  34. @Override
  35. public V remove(K k) throws CacheException {
  36. V v = redisTemplate.opsForValue().get(k);
  37. redisTemplate.opsForValue().getOperations().delete(k);
  38. return v;
  39. }
  40.  
  41. @Override
  42. public void clear() throws CacheException {
  43. }
  44.  
  45. @Override
  46. public int size() {
  47. return 0;
  48. }
  49.  
  50. @Override
  51. public Set<K> keys() {
  52. return null;
  53. }
  54.  
  55. @Override
  56. public Collection<V> values() {
  57. return null;
  58. }
  59. }

    自定义CachingSessionDAO

      继承EnterpriseCacheSessionDAO,然后重新设置其CacheManager(替换掉默认的内存缓存器),这样也可以实现我们的自定义CachingSessionDAO,但是这是优选吗;如若我们实现持久化,继承EnterpriseCacheSessionDAO是优选,但如果只是实现session缓存,那么CachingSessionDAO是优选,自定义更灵活。那么我们还是继承CachingSessionDAO来实现我们的自定义CachingSessionDAO

      ShiroSessionDAO

  1. package com.lee.shiro.config;
  2.  
  3. import org.apache.shiro.session.Session;
  4. import org.apache.shiro.session.mgt.eis.CachingSessionDAO;
  5. import org.springframework.stereotype.Component;
  6.  
  7. import java.io.Serializable;
  8.  
  9. @Component
  10. public class ShiroSessionDAO extends CachingSessionDAO {
  11.  
  12. @Override
  13. protected void doUpdate(Session session) {
  14. }
  15.  
  16. @Override
  17. protected void doDelete(Session session) {
  18. }
  19.  
  20. @Override
  21. protected Serializable doCreate(Session session) {
  22. // 这里绑定sessionId到session,必须要有
  23. Serializable sessionId = generateSessionId(session);
  24. assignSessionId(session, sessionId);
  25. return sessionId;
  26. }
  27.  
  28. @Override
  29. protected Session doReadSession(Serializable sessionId) {
  30. return null;
  31. }
  32. }

    最后将ShiroSessionDAO实例赋值给SessionManager实例,再讲SessionManager实例赋值给SecurityManager实例即可

    具体代码请参考spring-boot-shiro

  源码解析

    底层还是利用Filter + HttpServletRequestWrapper将对session的操作接入到自己的实现中来,而不走默认的servlet容器,这样对session的操作完全由我们自己掌握。

    shiro的session创建中其实讲到了shiro中对session操作的基本流程,这里不再赘述,没看的朋友可以先去看看再回过头来看这篇。本文只讲shiro中,如何将一个请求的session接入到自己的实现中来的;shiro中有很多默认的filter,我会单独开一篇来讲shiro的filter,这篇我们先不纠结这些filter。

    OncePerRequestFilter中doFilter方法如下

  1. public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
  2. throws ServletException, IOException {
  3. String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
  4. if ( request.getAttribute(alreadyFilteredAttributeName) != null ) { // 当前filter已经执行过了,进行下一个filter
  5. log.trace("Filter '{}' already executed. Proceeding without invoking this filter.", getName());
  6. filterChain.doFilter(request, response);
  7. } else //noinspection deprecation
  8. if (/* added in 1.2: */ !isEnabled(request, response) ||
  9. /* retain backwards compatibility: */ shouldNotFilter(request) ) { // 当前filter未被启用或忽略此filter,则进行下一个filter;shouldNotFilter已经被废弃了
  10. log.debug("Filter '{}' is not enabled for the current request. Proceeding without invoking this filter.",
  11. getName());
  12. filterChain.doFilter(request, response);
  13. } else {
  14. // Do invoke this filter...
  15. log.trace("Filter '{}' not yet executed. Executing now.", getName());
  16. request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
  17.  
  18. try {
  19. // 执行当前filter
  20. doFilterInternal(request, response, filterChain);
  21. } finally {
  22. // 一旦请求完成,我们清除当前filter的"已经过滤"的状态
  23. request.removeAttribute(alreadyFilteredAttributeName);
  24. }
  25. }
  26. }

    上图中,我可以看到AbstractShiroFilter的doFilterInternal放中将request封装成了shiro自定义的ShiroHttpServletRequest,将response也封装成了shiro自定义的ShiroHttpServletResponse。既然Filter中将request封装了ShiroHttpServletRequest,那么到我们应用的request就是ShiroHttpServletRequest类型,也就是说我们对session的操作最终都是由shiro完成的,而不是默认的servlet容器。

    另外补充一点,shiro的session创建不是懒创建的。servlet容器中的session创建是第一次请求session(第一调用request.getSession())时才创建。shiro的session创建如下图

    此时,还没登录,但是subject、session已经创建了,只是subject的认证状态为false,说明还没进行登录认证的。至于session创建过程已经保存到redis的流程需要大家自行去跟,或者阅读我之前的博文

总结

  1、当以集群方式对外提供服务的时候,不做session共享也是可以的

    可以通过ip_hash的机制将同个ip的请求定向到同一台后端,这样保证用户的请求始终是同一台服务处理,与单机应用基本一致了;但这有很多方面的缺陷(具体就不详说了),不推荐使用。

  2、servlet容器之间做session同步也是可以实现session共享的

    一个servlet容器生成session,其他节点的servlet容器从此servlet容器进行session同步,以达到session信息一致。这个也不推荐,某个时间点会有session不一致的问题,毕竟同步过程受到各方面的影响,不能保证session实时一致。

  3、session共享实现的原理其实都是一样的,都是filter + HttpServletRequestWrapper,只是实现细节会有所区别;有兴趣的可以看下spring-session的实现细节。

  4、如果我们采用的spring集成shiro,其实可以将缓存管理器交由spring管理,相当于由spring统一管理缓存。

  5、shiro的CacheManager不只是管理session缓存,还管理着身份认证缓存、授权缓存,shiro的缓存都是CacheManager管理。但是身份认证缓存默认是关闭的,个人也不推荐开启。

  6、shiro的session创建时机是在登录认证之前,而不是第一次调用getSession()时。

参考

  《跟我学shiro》

shiro源码篇 - shiro的session共享,你值得拥有的更多相关文章

  1. shiro源码篇 - shiro的filter,你值得拥有

    前言 开心一刻 已经报废了一年多的电脑,今天特么突然开机了,吓老子一跳,只见电脑管家缓缓地出来了,本次开机一共用时一年零六个月,打败了全国0%的电脑,电脑管家已经对您的电脑失去信心,然后它把自己卸载了 ...

  2. shiro源码篇 - shiro的session创建,你值得拥有

    前言 开心一刻 开学了,表弟和同学因为打架,老师让他回去叫家长.表弟硬气的说:不用,我打得过他.老师板着脸对他说:和你打架的那位同学已经回去叫家长了.表弟犹豫了一会依然硬气的说:可以,两个我也打得过. ...

  3. shiro源码篇 - shiro认证与授权,你值得拥有

    前言 开心一刻 我和儿子有个共同的心愿,出国旅游.昨天儿子考试得了全班第一,我跟媳妇合计着带他出国见见世面,吃晚饭的时候,一家人开始了讨论这个.我:“儿子,你的心愿是什么?”,儿子:“吃汉堡包”,我: ...

  4. shiro源码篇 - shiro的session的查询、刷新、过期与删除,你值得拥有

    前言 开心一刻 老公酷爱网络游戏,老婆无奈,只得告诫他:你玩就玩了,但是千万不可以在游戏里找老婆,不然,哼哼... 老公嘴角露出了微笑:放心吧亲爱的,我绝对不会在游戏里找老婆的!因为我有老公! 老婆: ...

  5. shiro源码篇 - 疑问解答与系列总结,你值得拥有

    前言 开心一刻 小明的朋友骨折了,小明去他家里看他.他老婆很细心的为他换药,敷药,然后出去买菜.小明满脸羡慕地说:你特么真幸福啊,你老婆对你那么好!朋友哭得稀里哗啦的说:兄弟你别说了,我幸福个锤子,就 ...

  6. Shiro源码解析-Session篇

    上一篇Shiro源码解析-登录篇中提到了在登录验证成功后有对session的处理,但未详细分析,本文对此部分源码详细分析下. 1. 分析切入点:DefaultSecurityManger的login方 ...

  7. 源码分析shiro认证授权流程

    1. shiro介绍 Apache Shiro是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理等功能: 认证 - 用户身份识别,常被称为用户“登录”: 授权 - 访问控制: 密码加密 ...

  8. Shiro 源码分析

    http://my.oschina.net/huangyong/blog/215153 Shiro 是一个非常优秀的开源项目,源码非常值得学习与研究. 我想尝试做一次 不一样 的源码分析:源码分析不再 ...

  9. Shiro源码分析之SecurityManager对象获取

    目录 SecurityManager获取过程 1.SecurityManager接口介绍 2.SecurityManager实例化时序图 3.源码分析 4.总结 @   上篇文章Shiro源码分析之获 ...

随机推荐

  1. mysql常用连接查询

    连接数据库PDO $user = "root"; //数据库连接账号 $pass = "root"; //数据库连接密码 $dbname = "tes ...

  2. Codeforces Round #486 (Div. 3) A. Diverse Team

    Codeforces Round #486 (Div. 3) A. Diverse Team 题目连接: http://codeforces.com/contest/988/problem/A Des ...

  3. ScriptOJ-unique#89

    一般做法 const unique = (arr) => { const result = arr.reduce((acc, iter) => { if(acc.indexOf(iter) ...

  4. leetcode437--Path Sum III

    https://leetcode.com/problems/path-sum-iii/ 理解比较困难,可以先看https://www.cnblogs.com/albert67/p/10416402.h ...

  5. Codeforces Round #514 (Div. 2) B - Forgery

    这个题我一开始没思路,最后也没思路 2个小时一直没思路 本来还想解释题意的,写了半天发现解释的不是很清楚,你还是google翻译一下吧 这个题解法是这样的: 首先,给你图案里面有很多的点,每个点的周围 ...

  6. Linux基础理论

    本节内容 1.  Linux的安装及相关配置 2.  UNIX和Linux操作系统概述 3.  Linux命令及帮助 4.  目录结构 6.  用户.群组和权限 7.  用户.群组和权限的深入讨论 1 ...

  7. Presto + Superset 数据仓库及BI

    基于Presto和superset搭建数据分析平台. Presto可以作为数据仓库,能够连接多种数据库和NoSql,同时查询性能很高: Superset提供了Presto连接,方便数据可视化和dash ...

  8. WITH RECOMPILE和OPTION(RECOMPILE)区别仅仅是存储过程级重编译和SQL语句级重编译吗

    在考虑重编译T-SQL(或者存储过程)的时候,有两种方式可以实现强制重编译(前提是忽略导致重编译的其他因素的情况下,比如重建索引,更新统计信息等等), 一是基于WITH RECOMPILE的存储过程级 ...

  9. 构建一个Gods Eye Android应用程序:第1部分 – 收集已安装的Android应用程序

    首先问候一下我的黑客伙伴们,在之前的Introduction to Amunet 教程中,我们了解到Amunet可能是一个间谍Android应用程序. 我不浪费太多时间因而直入主题. 在本教程中,我们 ...

  10. 处理返回键劫持(结合vue)

    在这里记录一下近期解决的一个问题 需求,在某个页面,浏览器返回按钮点击的时候,不能走浏览器的默认返回操作,而是要走自己的逻辑, 比如跳转页面等等. 那么问题来了,如何去不走默认返回呢.经过网上搜罗和同 ...