Spring Session处理Session丢失问题

一、会话跟踪技术

由于HTTP协议是无状态的协议,一次浏览器和服务器的交互过程就是:

  1. 浏览器:你好吗?
  2. 服务器:很好!

这就是一次会话,对话完成后,这次会话就结束了,服务器端并不能记住这个人,下次再对话时,服务器端并不知道是上一次的这个人,所以服务端需要记录用户的状态时,就需要用某种机制来识别具体的用户,这个机制就是会话跟踪技术

常用的会话跟踪技术:Cookie和URL重写。

1. Cookie

Cookie 是浏览器保存的一个小文件,其包含多个键值对。每次HTTP请求的时候,客户端都会发送相应的Cookie信息到服务端。

实际上大多数的应用都是用 Cookie 来实现Session跟踪的,第一次创建Session时,服务端会在HTTP协议中向客户端 Cookie 中记录一个Session ID,以后每次请求Cookie 请求头把这个会话ID发送到服务器,这样服务端就知道客户端是谁了。

  • 特点:
    • 参数存放:参数是存放在请求头部里的,也存在长度限制,但这个限制是服务器配置的限制,可以更改
    • Cookie禁用:可能会禁用Cookie
    • 持久性:浏览器可以保存Cookie一段时间,在此期间Cookie持续有效

2. URL 重写

当客户端的浏览器禁用了 Cookie 时,会使用URL重写技术来进行session会话跟踪。

每次HTTP交互,URL后面都会被附加上一个诸如 sessionId=xxxxx 这样的参数,服务端据此来识别客户端是谁

  • 特点:
    • 参数存放:参数是存放在 url 里的,有1024长度限制
    • Cookie禁用:当Cookie被禁用时依旧能够工作
    • 持久性:不存在持久性,一旦浏览器关闭就结束

二、Session机制存在的问题与解决方案

1. 存在的问题

在Web项目开发中,Session会话管理是一个很重要的部分,用于存储与记录用户的状态或相关的数据。

通常情况下session交由容器(tomcat)来负责存储和管理,但是如果项目部署在多台tomcat中,则session管理存在很大的问题

扫描二维码关注公众号,回复: 8813548 查看本文章
  • 多台tomcat之间无法共享session,比如用户在tomcat A服务器上已经登录了,但当负载均衡跳转到tomcat B时,由于tomcat B服务器并没有用户的登录信息,session就失效了,用户就退出了登录
  • 一旦tomcat容器关闭或重启也会导致session会话失效

2. 解决方案

  1. 第一种是使用容器扩展插件来实现,比如基于Tomcat的tomcat-redis-session-manager插件,基于Jetty的jetty-session-redis插件、memcached-session-manager插件;这个方案的好处是对项目来说是透明的,无需改动代码,但是由于过于依赖容器,一旦容器升级或者更换意味着又得重新配置
    其底层的原理是,复制session到其它服务器,所以会有一定的延迟,也不能部署太多的服务器。

  2. 第二种是使用Nginx负载均衡的ip_hash策略实现用户每次访问都绑定到同一台具体的后台tomcat服务器实现session总是存在
    这种方案的局限性是ip不能变,如果手机从北京跳到河北,那么ip会发生变化;另外负载均衡的时候,如果某一台服务器发生故障,那么会重新定位,也会跳转到别的机器;也有可能负载均衡时造成某个服务器请求繁忙。

  3. 第三种是自己写一套Session会话管理的工具类,在需要使用会话的时候都从自己的工具类中获取,而工具类后端存储可以放到Redis中,这个方案灵活性很好,但开发需要一些额外的时间。

  4. 第四种是使用框架的会话管理工具,也就是我们要介绍的Spring session,这个方案既不依赖tomcat容器,又不需要改动代码,由Spring session框架为我们提供,可以说是目前非常完美的session共享解决方案

三、Spring Session

1. 简介

Spring Session 是Spring家族中的一个子项目,它提供一组API和实现,用于管理用户的session信息

它把servlet容器实现的httpSession替换为spring-session,专注于解决 session管理问题,Session信息存储在Redis中,key是由UUID生成,且以hash结构存放在redis,可简单快速且无缝的集成到我们的应用中;

官网:https://spring.io/

Spring Session的特性:

  • 提供用户session管理的API和实现
  • 提供HttpSession,以中立的方式取代web容器的session,比如tomcat中的session
  • 支持集群的session处理,不必绑定到具体的web容器去解决集群下的session共享问题

2. SSM中使用SpringSession

(1)添加Spring Session相关依赖

Spring Session实现 Session 共享的原理是将Session中的键值对存放在 redis 数据库中,所以需要添加redis依赖

<!-- Spring session redis 依赖start -->
 <dependency>
     <groupId>org.springframework.session</groupId>
     <artifactId>spring-session-data-redis</artifactId>
     <version>1.3.1.RELEASE</version>
 </dependency>
 <!-- Spring session redis 依赖end -->
 <!-- spring web模块依赖 start -->
 <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-web</artifactId>
     <version>4.3.16.RELEASE</version>
 </dependency>
 <!-- spring web模块依赖end -->

(2)配置springSessionRepositoryFilter过滤器

springSessionRepositoryFilter过滤器获取到请求中的SessionId,然后从Redis中获取相应的Session

<filter>
    <filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSessionRepositoryFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

(3)配置SpringSession

配置Spring Session,关键点:

  • 启动Spring的注解支持:<context:annotation-config/>

  • 定义SpringSession的配置Bean:RedisHttpSessionConfiguration

    • 指定Cookie的序列化规则对象参数
    • 设置Session的最大生命周期
  • 自定义Cookie规则对象:DefaultCookieSerializer,这里要注意两个参数(后台区分是否是同一个session和路径和域名有关):

    • 解决同域名下不同项目的Session共享(Cookie的使用范围):设置 cookiePath 为域名的根路径

    例如:现在有两个项目,分别是:localhost:8080/web1和localhost:9090/web2,通过浏览器提供的开发人员工具(F12–>Application–>Cookies–>SESSION)可以发现,这两个请求的cookie的路径(path)不一致,一个是/web1,一个是/web2,虽然我们已经加了Spring
    Session共享机制,但是后台服务器认为这是两个不同的会话(session),故需要指定 cookiePath 参数为 “/”

    • 解决同根域名但是不同子域名的Session共享:设置 domainName 为要存放的根域名

    例如:还是上面的两个项目,只不过现在不再使用localhost了,而是使用域名,在C:\Windows\System32\drivers\etc\host文件中修改127.0.0.1的映射关系模拟不同的域名访问,这里使用的分别是:web1.web.com:8080/web1和web2.web.com:9090/web2,通过浏览器提供的开发人员工具可以发现,虽然这两个cookie的路径(path)都设置为了“/”,但是由于这两个cookie的域名(domain)不一致,虽然我们已经加了Spring
    Session共享机制,但是后台服务器同样认为这是两个不同的会话(session),故需要指定 domainName 参数为
    “web.com”

  • 配置 jedisConnectionFactory jedis连接工厂,用于连接redis

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <!--
        启动Spring的注解支持,由于SpringSession内部使用到了Spring的相关注解因此必须启动
        注意:
          <context:component-scan base-package="com"/> 包扫描配置,这个标签中包含了<context:annotation-config/>的全部功能
          因此实际工作中不需要指定<context:annotation-config/>
     -->
    <context:annotation-config/>
    <!--定义SpringSession的配置Bean -->
    <bean id="redisHttpSessionConfiguration" class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
        <!--指定Cookie的序列化规则对象,用于改变SpringSessionCookie的存放规则 -->
        <property name="cookieSerializer"  ref="defaultCookieSerializer"/>

 		<!--设置Session的最大生命周期,单位为秒 默认有效期为1800 表示30分钟 -->
        <!--<property name="maxInactiveIntervalInSeconds" value="1800"/>-->
    </bean>

    <!--自定义Cookie规则对象 -->
    <bean id="defaultCookieSerializer" class="org.springframework.session.web.http.DefaultCookieSerializer">
        <!--指定SpringSession的Cookie数据要存放到域名的根路径下,用于解决同域名下不同项目的Session共享 -->
        <property name="cookiePath" value="/"/>
        <!--指定SpringSession的Cookie数据要存放到根域名下,用于解决同根域名但是不同子域名的Session共享,
        低版本的Tomcat(版本小于8)如果报错要在根域名前多加个点.-->
        <property name="domainName" value="myweb.com"/>
    </bean>

    <!-- 配置jedis连接工厂,用于连接redis
        如果工作中项目已经集成了Redis那么这个配置和可以不写的
     -->
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="192.168.245.128"/>
        <property name="port" value="6379"/>
        <property name="password" value="123456"/>
        <property name="usePool" value="true"/>
    	<property name="timeout" value="15000"/>
    </bean>

</beans>

3. SpringBoot中使用SpringSession

(1)添加依赖

<!--spring连接redis的起步依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--springSession的依赖-->
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>

(2)配置参数

#连接redis三要素:ip,端口,密码
spring.redis.host=192.168.29.128
spring.redis.port=6379
spring.redis.password=123456

#设置SessionId存放在Cookie中的什么路径中 /表示根路径,用于解决同域名下不同项目的Session共享
server.servlet.session.cookie.path=/

#设置SessionId存放在Cookie中的根域名,用于解决同根域名不同二级子域名的Session共享
server.servlet.session.cookie.domain=web.com

#设置Session的最大生命周期,默认值为30m 表示30分钟
server.servlet.session.timeout=30m

4. 单点登录

不同根域名下的项目实现Session共享,比如阿里巴巴这样的公司,有多个业务线,多个网站,用户在一个网站登录,那么其他网站也会是登录了的状态,比如:登录了淘宝网,则天猫网也是登录的;

  • www.taobao.com
  • www.tmall.com

对于不同根域名的场景,要实现一处登录,处处登录,Spring Session不支持单点登录(Single Sign On),简称为 SSO,是流行的企业业务整合的解决方案之一,SSO是指在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统

四、源码的简单分析

  1. 页面请求被全局的过滤器org.springframework.web.filter.DelegatingFilterProxy过滤
  2. 全局的过滤器是一个代理过滤器,它不执行真正的过滤逻辑,它代理了一个Spring容器中的名为:springSessionRepositoryFilter 的一个过滤器
  3. 代理的这个 springSessionRepositoryFilter 过滤器是从spring容器中获取的,真正执行过滤逻辑的是 SessionRepositoryFilter
  4. 该SessionRepositoryFilter过滤器覆盖了原来servlet中的request和response接口中定义的操作session方法,替换成自己的session方法
  5. 在过滤的时候,总是会执行一个finally语句块,在finally中提交session,保存到Redis的session以hash结构存放在redis中
发布了45 篇原创文章 · 获赞 46 · 访问量 1815

猜你喜欢

转载自blog.csdn.net/zyx1260168395/article/details/103880228
今日推荐