欢迎您的访问
专注于分享最有价值的互联网技术干货

Spring Session解决Session共享

几个T的资料等你来白嫖
双倍快乐
一定要收藏这个宝藏网站防止丢失,求助资源~!!!

一、前言

由于HTTP协议是无状态的协议,一次浏览器和服务器的交互过程就是一次会话,对话完成后,这次会话就结束了,服务器端并不能记住这个人,下次再对话时,服务器端并不知道是上一次的这个人,所以服务端需要记录用户的状态时,就需要用某种机制来识别具体的用户,这个机制就是Session,服务端如何识别特定的客户?这个时候需要使用Cookie;每次HTTP请求的时候,客户端都会发送相应的Cookie信息到服务端。实际上大多数的应用都是用 Cookie 来实现Session跟踪的,第一次创建Session时,服务端会在HTTP协议中向客户端 Cookie 中记录一个Session ID,以后每次请求把这个会话ID发送到服务器,这样服务端就知道客户端是谁了;那么如果客户端的浏览器禁用了 Cookie 怎么办? 一般这种情况下,会使用一种叫做URL重写的技术来进行session会话跟踪,即每次HTTP交互,URL后面都会被附加上一个诸如 sessionId=xxxxx 这样的参数,服务端据此来识别客户端是谁。

二、Session会话管理

在Web项目开发中,Session会话管理是一个很重要的部分,用于存储与记录用户的状态或相关的数据;通常情况下session交由容器(tomcat)来负责存储和管理,但是如果项目部署在多台tomcat中,则session管理存在很大的问题。

1、多台tomcat之间无法共享session,比如用户在tomcat A服务器上已经登录了,但当负载均衡跳转到tomcat B时,由于tomcat B服务器并没有用户的登录信息,session就失效了,用户就退出了登录。
2、一旦tomcat容器关闭或重启也会导致session会话失效;因此如果项目部署在多台tomcat中,就需要解决session共享的问题。

三、Session会话共享方案

第一种是使用容器扩展插件来实现,比如基于Tomcat的tomcat-redis-session-manager插件,基于Jetty的jetty-session-redis插件、memcached-session-manager插件;这个方案的好处是对项目来说是透明的,无需改动代码,但是由于过于依赖容器,一旦容器升级或者更换意味着又得重新配置。

第二种是使用Nginx负载均衡的ip_hash策略实现用户每次访问都绑定到同一台具体的后台tomcat服务器实现session总是存在。

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

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

四、Spring Session简介

Spring Session 是Spring家族中的一个子项目,Spring Session提供了用于管理用户会话信息的API和实现。

它把servlet容器实现的httpSession替换为spring-session,专注于解决 session管理问题,Session信息存储在Redis中,可简单快速且无缝的集成到我们的应用中。

Spring Session官网地址:https://spring.io/projects/spring-session

Spring Session的特性:

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

五、Spring Session示例

使用Spring session管理session,开发步骤如下:

5-1、添加案例所需要的maven依赖

<!-- Spring Session 依赖start -->
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session</artifactId>
    <version>1.3.1.RELEASE</version>
</dependency>
<!-- Spring Session 依赖end -->

<!-- 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-data-redis依赖的JAR配置start -->
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>1.8.8.RELEASE</version>
</dependency>
<!-- spring-data-redis依赖的JAR配置end -->

<!-- jedis依赖的JAR配置start -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>
<!-- jedis依赖的JAR配置end -->

<!-- spring web模块依赖 start -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.0.0.RELEASE</version>
</dependency>
<!-- spring web模块依赖end -->

<!-- servlet依赖的jar包start -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
</dependency>
<!-- servlet依赖的jar包start -->

<!-- jsp依赖jar包start -->
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>javax.servlet.jsp-api</artifactId>
    <version>2.3.1</version>
</dependency>
<!-- jsp依赖jar包end -->

<!--jstl标签依赖的jar包start -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>
<!--jstl标签依赖的jar包end -->

5-2、在web.xml文件文件中配置springSessionRepositoryFilter过滤器

<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>

5-3、在web.xml文件中加载Spring配置文件

<!-- needed for ContextLoaderListener -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>

    <!-- Bootstraps the root web application context before servlet initialization -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

5-4、Spring配置文件配置一个RedisHttpSessionConfiguration类

<!-- spring注解、bean的处理器 -->
    <context:annotation-config/>

    <!-- Spring session 的配置类 -->
    <bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
        <!--指定session策略-->
        <!--<property name="httpSessionStrategy" ref="httpSessionStrategy"/>-->
    </bean>

    <bean id="httpSessionStrategy" class="org.springframework.session.web.http.CookieHttpSessionStrategy">
        <!--使用cookie策略-->
        <property name="cookieSerializer" ref="cookieSerializer"/>
    </bean>

    <bean id="cookieSerializer" class="org.springframework.session.web.http.DefaultCookieSerializer">
        <!--指定cookie的域名及路径-->
        <property name="cookiePath" value="/"/>
        <property name="domainName" value="web.com"/>
    </bean>

5-5、Spring配置文件配置Spring-data-redis

<!-- 配置jedis连接工厂,用于连接redis -->
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
    <property name="hostName" value="${redis.hostName}"/>
    <property name="port" value="${redis.port}"/>
    <property name="password" value="${redis.password}"/>
    <property name="usePool" value="${redis.usePool}"/>
    <property name="timeout" value="${redis.timeout}"/>
</bean>

<!--读取redis.properties属性配置文件-->
<context:property-placeholder location="classpath:redis.properties"/>

5-6、redis配置文件

redis.hostName=192.168.184.133
redis.port=6379
redis.password=123456
redis.usePool=true
redis.timeout=10000

5-7、编写一个Servlet或JSP验证session共享问题

@WebServlet(name = "SessionServlet", urlPatterns = "/session")
public class SessionServlet extends HttpServlet {

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String sesssionID = request.getSession().getId();
        //部署两份,把这个地方8081改成8080就行了,只是为了区分
        response.getWriter().write("8081 Server SessionID"+sesssionID);
    }
}

六、演示案例

6-1、同域名下相同项目实现Session共享

1、在同一个域名下,比如:www.web.com
2、同一个项目
3、部署了多台tomcat

这就是典型的集群,使用Nginx负载均衡,每次发请求在两个tomcat容器之间负载均衡,这也是Nginx的默认策略

20210203152723911.png
20210203152723970.png
20210203152724018.png

如上图,很显然看到的是负载均衡,然后放session,默认浏览器中没有session,会自动创键,看到8081服务器的sessionid为

795b073c-0e27-4339-bf01-2fb7d39a3ed2f

20210203152724061.png

查看redis,可以看到该session信息存储到Redis里面了,Redis仅仅是一个容器,也可以使用其他容器

20210203152724108.png
20210203152724160.png

#定时Job程序触发Session 过期。(spring-session 功能),数据类型:Set
Key:spring:session:expirations:XXXXX
 
# 存储 Session 数据,数据类型hash
Key:spring:session:sessions:XXXXXXX
 
# Redis TTL触发Session 过期。(Redis 本身功能),数据类型:String
Key:spring:session:sessions:expires:XXXXX

20210203152724214.png

在次执行请求,负载均衡,跳转到8080服务器,8080服务器上是没有session的会新生成一个sessionid,但是这时候没有新生产成sessionid,而是在使用8081生成的sessionid,这就达成了session共享。

6-2、同域名下不同项目实现Session共享

在同一个域名下,比如:

www.web.com/jiekuan
www.web.com/touzi

有多个不同的项目

20210203152724530.png

做法:设置Cookie路径为根/上下文

6-3、同根域名不同二级子域名下的项目实现Session共享

同一个根域名,比如:

www.web.com
beijing.web.com
nanjing.web.com

不同的二级子域名

20210203152724837.png

做法:

1、设置Cookie路径为根/上下文
2、设置域名为同域名 web.com

6-4、不同根域名下的项目实现Session共享

1、单点登录

1、单点登录(Single Sign On),简称为 SSO,是流行的企业业务整合的解决方案之一,SSO是指在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
2、比如阿里巴巴这样的公司,有多个业务线,多个网站,用户在一个网站登录,那么其他网站也会是登录了的状态,比如:登录了淘宝网,则天猫网也是登录的。

2、比如:

www.web.com
www.myweb.com
www.webtest.com
ip和域名也会产生不同的sessionid

3、对于不同根域名的场景,要实现一处登录,处处登录,Spring Session不支持

赞(0) 打赏
版权归原创作者所有,任何形式转载请联系我们:大白菜博客 » Spring Session解决Session共享

评论 抢沙发

0 + 6 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏