您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

Spring Session 原理分析

时间:2020-03-29 09:30:50  来源:  作者:

为什么要分布式 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 {

  @Override
  protected 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;
  }

  @Override
  protected 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 方法
   */
  @Override
  public 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);
    }

    @Override
    public void invalidate() {
      super.invalidate();
      /** session 不合法,从存储中删除信息 */
      SessionRepositoryFilter.this.sessionRepository.deleteById(getId());
    }
  }
}

原理简单,装饰 HttpSession , Session 失效时从存储中删除,在请求结束之后,存储 session 。

总结

分布式环境下的认证方案: JWT 与分布式 Session 。

个人觉得两种方案都很好, JWT ,无状态,服务器不用维护 Session 信息,但如何让 JWT 失效是一个难题。

分布式 Session ,使用起来简单,但需要额外的存储空间。

实际应用中,要兼顾当前的业务场景与安全性进行方案的选择。



Tags:Spring   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
想要了解Spring MVC框架的原理,探究框架是如何设计的,不错的学习方式是阅读源码,然后自己手写一个框架。本文带领大家简化的手写一个Spring MVC框架。Spring框架对于Java后端程...【详细内容】
2021-09-22  Tags: Spring  点击:(53)  评论:(0)  加入收藏
前言一个基于spring boot的JAVA开源商城系统,是前后端分离、为生产环境多实例完全准备、数据库为b2b2c商城系统设计、拥有完整下单流程和精美设计的java开源商城系统https://...【详细内容】
2021-09-17  Tags: Spring  点击:(119)  评论:(0)  加入收藏
在 Java 和 Kotlin 中, 除了使用Spring Boot创建微服务外,还有很多其他的替代方案。 名称 版本 发布时间 开发商 GitHub ...【详细内容】
2021-08-06  Tags: Spring  点击:(175)  评论:(0)  加入收藏
大家都知道,MyBatis 框架是一个持久层框架,是 Apache 下的顶级项目。Mybatis 可以让开发者的主要精力放在 sql 上,通过 Mybatis 提供的映射方式,自由灵活的生成满足需要的 sql 语句。使用简单的 XML 或注解来配置和映射原...【详细内容】
2021-07-06  Tags: Spring  点击:(96)  评论:(0)  加入收藏
首先,先看 SpringBoot 的主配置类:@SpringBootApplicationpublicclassStartEurekaApplication{publicstaticvoidmain(String[] args){SpringApplication.run(StartEurekaAppli...【详细内容】
2021-06-11  Tags: Spring  点击:(144)  评论:(0)  加入收藏
环境:Spring5.3.3Spring容器启动时,创建 DefaultListableBeanFactory 工厂实例化 AnnotationConfigApplicationContext对象public AnnotationConfigApplicationContext(String...【详细内容】
2021-06-10  Tags: Spring  点击:(152)  评论:(0)  加入收藏
现在主流的Web MVC框架除了Struts这个主力 外,其次就是Spring MVC了,因此这也是作为一名程序员需要掌握的主流框架,框架选择多了,应对多变的需求和业务时,可实行的方案自然就多了...【详细内容】
2021-05-27  Tags: Spring  点击:(195)  评论:(0)  加入收藏
目前,Spring Cloud Gateway是仅次于Spring Cloud Netflix的第二个最受欢迎的Spring Cloud项目(就GitHub上的星级而言)。它是作为Spring Cloud系列中Zuul代理的继任者而创建的。...【详细内容】
2021-04-21  Tags: Spring  点击:(426)  评论:(0)  加入收藏
在使用Spring MVC的时候,标准的配置是如下这样的: 注意注意:小编整理了一份Spring全家桶笔记:Spring+Spring Boot+Spring Cloud+Spring MVC,有需要的朋友可以私信“spring”免费...【详细内容】
2021-04-13  Tags: Spring  点击:(239)  评论:(0)  加入收藏
今天又要给大家介绍一个 Spring Boot 中的组件 --HandlerMethodReturnValueHandler。在前面的文章中(如何优雅的实现 Spring Boot 接口参数加密解密?),松哥已经和大家介绍过如何...【详细内容】
2021-03-24  Tags: Spring  点击:(297)  评论:(0)  加入收藏
▌简易百科推荐
面向对象的特征之一封装 面向对象的特征之二继承 方法重写(override/overWrite) 方法的重载(overload)和重写(override)的区别: 面向对象特征之三:多态 Instanceof关键字...【详细内容】
2021-12-28  顶顶架构师    Tags:面向对象   点击:(2)  评论:(0)  加入收藏
一、Redis使用过程中一些小的注意点1、不要把Redis当成数据库来使用二、Arrays.asList常见失误需求:把数组转成list集合去处理。方法:Arrays.asList 或者 Java8的stream流式处...【详细内容】
2021-12-27  CF07    Tags:Java   点击:(3)  评论:(0)  加入收藏
文章目录 如何理解面向对象编程? JDK 和 JRE 有什么区别? 如何理解Java中封装,继承、多态特性? 如何理解Java中的字节码对象? 你是如何理解Java中的泛型的? 说说泛型应用...【详细内容】
2021-12-24  Java架构师之路    Tags:JAVA   点击:(5)  评论:(0)  加入收藏
大家好!我是老码农,一个喜欢技术、爱分享的同学,从今天开始和大家持续分享JVM调优方面的经验。JVM调优是个大话题,涉及的知识点很庞大 Java内存模型 垃圾回收机制 各种工具使用 ...【详细内容】
2021-12-23  小码匠和老码农    Tags:JVM调优   点击:(11)  评论:(0)  加入收藏
前言JDBC访问Postgresql的jsonb类型字段当然可以使用Postgresql jdbc驱动中提供的PGobject,但是这样在需要兼容多种数据库的系统开发中显得不那么通用,需要特殊处理。本文介绍...【详细内容】
2021-12-23  dingle    Tags:JDBC   点击:(13)  评论:(0)  加入收藏
Java与Lua相互调用案例比较少,因此项目使用需要做详细的性能测试,本内容只做粗略测试。目前已完成初版Lua-Java调用框架开发,后期有时间准备把框架进行抽象,并开源出来,感兴趣的...【详细内容】
2021-12-23  JAVA小白    Tags:Java   点击:(11)  评论:(0)  加入收藏
Java从版本5开始,在 java.util.concurrent.locks包内给我们提供了除了synchronized关键字以外的几个新的锁功能的实现,ReentrantLock就是其中的一个。但是这并不意味着我们可...【详细内容】
2021-12-17  小西学JAVA    Tags:JAVA并发   点击:(11)  评论:(0)  加入收藏
一、概述final是Java关键字中最常见之一,表示“最终的,不可更改”之意,在Java中也正是这个意思。有final修饰的内容,就会变得与众不同,它们会变成终极存在,其内容成为固定的存在。...【详细内容】
2021-12-15  唯一浩哥    Tags:Java基础   点击:(17)  评论:(0)  加入收藏
1、问题描述关于java中的日志管理logback,去年写过关于logback介绍的文章,这次项目中又优化了下,记录下,希望能帮到需要的朋友。2、解决方案这次其实是碰到了一个问题,一般的情况...【详细内容】
2021-12-15  软件老王    Tags:logback   点击:(19)  评论:(0)  加入收藏
本篇文章我们以AtomicInteger为例子,主要讲解下CAS(Compare And Swap)功能是如何在AtomicInteger中使用的,以及提供CAS功能的Unsafe对象。我们先从一个例子开始吧。假设现在我们...【详细内容】
2021-12-14  小西学JAVA    Tags:JAVA   点击:(21)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条