Spring Session 原理分析

为什么要分布式 Session 呢?
请参考下图:

Spring Session 原理分析

文章插图
 
当后台集群部署时,单机的 Session 维护就会出现问题 。
假设登录的认证授权发生在 Tomcat A 服务器上,Tomcat A 在本地存储了用户 Session,并签发认证令牌,用于验证用户身份 。
下次请求可能分发给 Tomcat B 服务器,而 Tomcat B 并没有用户 Session,用户携带的认证令牌无效,得到 401。
Spring Session 原理分析

文章插图
 
除了 JWT 无状态的认证方式,另一种主流的实现方案就是采用分布式 Session。
public interface HttpSession {public void setAttribute(String name, Object value);}HttpSession 内的存储就是 name 与 value 的键值对映射,且存在过期时间,这与 redis 的设计相符合,分布式 Session 通常使用 Redis 进行实现 。
无论是在单机环境,还是在引入了 Spring Session 的集群环境下,代码实现都是相同的,即屏蔽了底层的细节,可以在不改动 HttpSession 使用的相关代码的情况下,实现 Session 存储环境的切换 。
logger.debug("记录当前用户ID");httpSession.setAttribute(UserService.USER_ID, persistUser.getId());这听起来很酷,那么 Spring Session 具体是如何在不改动代码的情况下进行 Session 存储环境切换的呢?
原理官方文档: How HttpSession Integration Works - Spring Session
回顾之前在学习 Spring Security 原理之时,我们从官方文档中找到了这样一张图 。
Spring Session 原理分析

文章插图
 
所有的认证授权拦截都是基于 Filter 实现的,而这里的 Spring Session,也是基于 Filter。
原理分析因为 HttpSession 和 HttpServletRequest (获取 HttpSession 的 API )都是接口,这意味着可以将这些 API 替换成自定义的实现 。
核心源码如下:
注:以下代码中部分无关代码已被删减 。public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {/** 替换 request */SessionRepositoryRequestWrApper wrappedRequest = new SessionRepositoryRequestWrapper(request, response, this.servletContext);/** 替换 response */SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest, response);/** try-finally,finally 必定执行 */try {/** 执行后续过滤器链 */filterChain.doFilter(wrappedRequest, wrappedResponse);} finally {/** 后续过滤器链执行完毕,提交 session,用于存储 session 信息并返回 set-cookie 信息 */wrappedRequest.commitSession();}}}response 封装器核心源码如下:
private final class SessionRepositoryResponseWrapper extends OnCommittedResponseWrapper {SessionRepositoryResponseWrapper(SessionRepositoryRequestWrapper request, HttpServletResponse response) {super(response);this.request = request;}@Overrideprotected void onResponseCommitted() {/** response 提交后提交 session */this.request.commitSession();}}request 封装器核心源码如下:
private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {private SessionRepositoryRequestWrapper(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) {super(request);this.response = response;this.servletContext = servletContext;}/*** 将 sessionId 写入 reponse,并持久化 session*/private void commitSession() {/** 获取当前 session 信息 */S session = getCurrentSession().getSession();/** 持久化 session */SessionRepositoryFilter.this.sessionRepository.save(session);/** reponse 写入 sessionId */SessionRepositoryFilter.this.httpSessionIdResolver.setSessionId(this, this.response, session.getId());}/*** 重写 HttpServletRequest 的 getSession 方法*/@Overridepublic HttpSessionWrapper getSession(boolean create) {/** 从持久化中查询 session */S requestedSession = getRequestedSession();/** session 存在,直接返回 */if (requestedSession != null) {currentSession = new HttpSessionWrapper(requestedSession, getServletContext());currentSession.setNew(false);return currentSession;}/** 设置不创建,返回空 */if (!create) {return null;}/** 创建 session 并返回 */S session = SessionRepositoryFilter.this.sessionRepository.createSession();currentSession = new HttpSessionWrapper(session, getServletContext());return currentSession;}/*** 从 repository 查询 session*/private S getRequestedSession() {/** 查询 sessionId 信息 */List<String> sessionIds = SessionRepositoryFilter.this.httpSessionIdResolver.resolveSessionIds(this);/** 遍历查询 */for (String sessionId : sessionIds) {S session = SessionRepositoryFilter.this.sessionRepository.findById(sessionId);if (session != null) {this.requestedSession = session;break;}}/** 返回持久化 session */return this.requestedSession;}/*** http session 包装器*/private final class HttpSessionWrapper extends HttpSessionAdapter<S> {HttpSessionWrapper(S session, ServletContext servletContext) {super(session, servletContext);}@Overridepublic void invalidate() {super.invalidate();/** session 不合法,从存储中删除信息 */SessionRepositoryFilter.this.sessionRepository.deleteById(getId());}}}


推荐阅读