Spring Session提供了用于管理用户会话信息的API和实现。

1.简介

Spring Session提供了用于管理用户会话信息的API和实现,同时还使得支持群集会话变得微不足道,而不依赖于特定于应用程序容器的解决方案。它还提供透明集成:

  • HttpSession - 允许替换应用程序容器(即Tomcat)中立方式中的HttpSession,支持在头文件中提供会话ID以使用RESTful API。

  • WebSocket - 提供在接收WebSocket消息时保持HttpSession活动的能力

  • WebSession - 允许以应用程序容器中立方式替换Spring WebFlux的WebSession

2. 2.0中有什么新功能

以下是Spring Session 2.0中新内容的重点。您可以参考的更新日志找到一个什么样的新的完整列表 2.0.0.M12.0.0.M22.0.0.M32.0.0.M42.0.0.M52.0.0.RC12.0.0.RC22.0.0.RELEASE

3.样品和指南(从这里开始)

如果您希望开始使用Spring Session,最好的起点是我们的示例应用程序。

表1.使用Spring Boot的示例应用程序
资源 描述 指南

与Redis的HttpSession

演示如何使用Spring Session将HttpSession替换为Redis。

HttpSession与Redis指南

使用JDBC的HttpSession

演示如何使用Spring Session将HttpSession替换为关系数据库存储。

带有JDBC指南的HttpSession

按用户名查找

演示如何使用Spring Session按用户名查找会话。

按用户名指南查找

的WebSockets

演示如何将Spring Session与WebSockets一起使用。

WebSockets指南

WebFlux

演示如何使用Spring Session用Redis替换Spring WebFlux的WebSession

TBD

使用Redis JSON序列化的HttpSession

演示如何使用Spring Session使用JSON序列化将Redis替换为HttpSession

TBD

表2.使用Spring基于Java的配置的示例应用程序
资源 描述 指南

与Redis的HttpSession

演示如何使用Spring Session将HttpSession替换为Redis。

HttpSession与Redis指南

使用JDBC的HttpSession

演示如何使用Spring Session将HttpSession替换为关系数据库存储。

带有JDBC指南的HttpSession

HttpSession与Hazelcast

演示如何使用Spring Session将HttpSession替换为Hazelcast。

HttpSession与Hazelcast指南

自定义Cookie

演示如何使用Spring Session并自定义cookie。

自定义Cookie指南

Spring Security

演示如何将Spring Session与现有的Spring Security应用程序一起使用。

Spring Security指南

休息

演示如何在REST应用程序中使用Spring Session来支持使用标头进行身份验证。

REST指南

表3.使用基于Spring XML的配置的示例应用程序
资源 描述 指南

与Redis的HttpSession

演示如何使用Spring Session将HttpSession替换为Redis商店。

HttpSession与Redis指南

使用JDBC的HttpSession

演示如何使用Spring Session将HttpSession替换为关系数据库存储。

带有JDBC指南的HttpSession

表4.杂项样本应用
资源 描述 指南

Grails 3

演示如何将Spring Session与Grails 3一起使用。

Grails 3指南

Hazelcast

演示如何在Java EE应用程序中使用Spring Session和Hazelcast。

TBD

4. Spring Session模块

在Spring Session 1.x中,所有Spring Session的SessionRepository实现都在spring-session工件中可用。虽然方便,但随着更多功能和SessionRepository实施被添加到项目中,这种方法不可能长期持续。

从Spring Session 2.0开始,该项目已分解为Spring Session核心模块,以及其他几个带有与特定数据存储相关的SessionRepository实现和功能的模块。Spring Data的用户会发现这种安排很熟悉,Spring Session核心模块扮演的角色相当于Spring Data Commons,并提供核心功能和API以及包含数据存储特定实现的其他模块。作为此拆分的一部分,Spring Session Data MongoDB和Spring Session Data GemFire模块被移动到单独的存储库,因此项目的存储库/模块的情况如下:

最后,Spring Session现在还提供Maven BOM(如“材料清单”)模块,以帮助用户解决版本管理问题:

5. HttpSession集成

Spring Session提供与HttpSession的透明整合。这意味着开发人员可以使用Spring Session支持的实现切换HttpSession实现。

5.1.为什么Spring Session&HttpSession?

我们已经提到Spring Session提供与HttpSession的透明集成,但我们从中获得了哪些好处?

  • 集群会话 - Spring Session使支持集群会话变得微不足道,而不依赖于特定于应用程序容器的解决方案。

  • RESTful API - Spring Session允许在标头中提供会话ID以使用RESTful API

5.2.与Redis的HttpSession

通过在使用HttpSession的任何内容之前添加Servlet过滤器来启用Spring Session和HttpSession您可以选择启用此功能:

5.2.1.Redis基于Java的配置

本节介绍如何使用Redis使用基于Java的配置支持HttpSession

HttpSession的样品提供了关于如何整合Spring Session,并使用Java配置HttpSession工作示例。您可以在下面阅读集成的基本步骤,但建议您在与自己的应用程序集成时遵循详细的HttpSession指南。
Spring Java配置

添加所需的依赖项后,我们可以创建Spring配置。Spring配置负责创建一个Servlet过滤器,用Spring Session支持的实现替换HttpSession实现。添加以下Spring配置:

@EnableRedisHttpSession (1)
public class Config {

	@Bean
	public LettuceConnectionFactory connectionFactory() {
		return new LettuceConnectionFactory(); (2)
	}
}
1 @EnableRedisHttpSession注释创建一个名为springSessionRepositoryFilter的Spring Bean,它实现了Filter。过滤器负责替换Spring Session支持的HttpSession实现。在这种情况下,Spring Session由Redis支持。
2 我们创建一个RedisConnectionFactory连接Spring Session到Redis服务器。我们将连接配置为在默认端口上连接到localhost(6379)有关配置Spring Data Redis的更多信息,请参阅参考文档
Java Servlet容器初始化

我们的Spring配置创建了一个名为springSessionRepositoryFilter的Spring Bean,它实现了FilterspringSessionRepositoryFilter bean负责使用Spring Session支持的自定义实现替换HttpSession

为了使我们的Filter能够发挥其魔力,Spring需要加载我们的Config类。最后,我们需要确保我们的Servlet容器(即Tomcat)对每个请求都使用springSessionRepositoryFilter幸运的是,Spring Session提供了一个名为AbstractHttpSessionApplicationInitializer的实用程序类,这两个步骤都非常简单。你可以在下面找到一个例子:

的src /主/爪哇/样品/ Initializer.java
public class Initializer extends AbstractHttpSessionApplicationInitializer { (1)

	public Initializer() {
		super(Config.class); (2)
	}
}
我们班级的名字(初始化程序)并不重要。重要的是我们延长AbstractHttpSessionApplicationInitializer
1 第一步是扩展AbstractHttpSessionApplicationInitializer这确保了名称springSessionRepositoryFilter的Spring Bean在每个请求中都注册了我们的Servlet容器。
2 AbstractHttpSessionApplicationInitializer还提供了一种机制,可以轻松确保Spring加载我们的Config

5.2.2.基于Redis XML的配置

本节介绍如何使用Redis使用基于XML的配置支持HttpSession

HttpSession的XML示例提供了关于如何整合Spring Session和使用XML配置HttpSession工作示例。您可以在下面阅读集成的基本步骤,但建议您在与自己的应用程序集成时遵循详细的HttpSession XML指南。
Spring XML配置

添加所需的依赖项后,我们可以创建Spring配置。Spring配置负责创建一个Servlet过滤器,用Spring Session支持的实现替换HttpSession实现。添加以下Spring配置:

的src /主/ web应用/ WEB-INF /弹簧/ session.xml
(1)
<context:annotation-config/>
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>

(2)
<bean class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory"/>
1 我们使用<context:annotation-config/>RedisHttpSessionConfiguration的组合,因为Spring Session尚未提供XML命名空间支持(请参阅gh-104)。这将创建一个名为springSessionRepositoryFilter的Spring Bean,它实现了Filter。过滤器负责替换Spring Session支持的HttpSession实现。在这种情况下,Spring Session由Redis支持。
2 我们创建一个RedisConnectionFactory,将Spring Session连接到Redis服务器。我们将连接配置为在默认端口上连接到localhost(6379)有关配置Spring Data Redis的更多信息,请参阅参考文档
XML Servlet容器初始化

我们的Spring配置创建了一个名为springSessionRepositoryFilter的Spring Bean,它实现了FilterspringSessionRepositoryFilter bean负责使用Spring Session支持的自定义实现替换HttpSession

为了让我们的Filter能够发挥其魔力,我们需要指示Spring加载我们的session.xml配置。我们使用以下配置执行此操作:

的src /主/ web应用/ WEB-INF / web。XML
<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>
		/WEB-INF/spring/*.xml
	</param-value>
</context-param>
<listener>
	<listener-class>
		org.springframework.web.context.ContextLoaderListener
	</listener-class>
</listener>

ContextLoaderListener的读取contextConfigLocation的,拿起我们的session.xml配置。

最后,我们需要确保我们的Servlet容器(即Tomcat)对每个请求都使用springSessionRepositoryFilter以下代码段为我们执行了最后一步:

的src /主/ web应用/ WEB-INF / web。XML
<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>
	<dispatcher>REQUEST</dispatcher>
	<dispatcher>ERROR</dispatcher>
</filter-mapping>

DelegatingFilterProxy的将查找一个Bean由springSessionRepositoryFilter的名称,并将其转换为Filter对于每个调用DelegatingFilterProxy的请求,都将调用springSessionRepositoryFilter

5.3.使用JDBC的HttpSession

通过在使用HttpSession的任何内容之前添加Servlet过滤器来启用Spring Session和HttpSession您可以选择启用此功能:

5.3.1.JDBC基于Java的配置

本节介绍如何使用关系数据库使用基于Java的配置支持HttpSession

所述的HttpSession JDBC样品提供了关于如何整合Spring Session,并使用Java配置HttpSession工作示例。您可以在下面阅读集成的基本步骤,但是在与您自己的应用程序集成时,建议您遵循详细的HttpSession JDBC指南。
Spring Java配置

添加所需的依赖项后,我们可以创建Spring配置。Spring配置负责创建一个Servlet过滤器,用Spring Session支持的实现替换HttpSession实现。添加以下Spring配置:

@EnableJdbcHttpSession (1)
public class Config {

	@Bean
	public EmbeddedDatabase dataSource() {
		return new EmbeddedDatabaseBuilder() (2)
				.setType(EmbeddedDatabaseType.H2)
				.addScript("org/springframework/session/jdbc/schema-h2.sql").build();
	}

	@Bean
	public PlatformTransactionManager transactionManager(DataSource dataSource) {
		return new DataSourceTransactionManager(dataSource); (3)
	}

}
1 @EnableJdbcHttpSession注释创建一个名为springSessionRepositoryFilter的Spring Bean,它实现了Filter。过滤器负责替换Spring Session支持的HttpSession实现。在这种情况下,Spring Session由关系数据库支持。
2 我们创建了一个dataSource,它将Spring Session连接到H2数据库的嵌入式实例。我们将H2数据库配置为使用Spring Session中包含的SQL脚本创建数据库表。
3 我们创建了一个transactionManager来管理先前配置的dataSource的事务。

有关如何配置与数据访问相关的问题的其他信息,请参阅Spring Framework参考文档

Java Servlet容器初始化

我们的Spring配置创建了一个名为springSessionRepositoryFilter的Spring Bean,它实现了FilterspringSessionRepositoryFilter bean负责使用Spring Session支持的自定义实现替换HttpSession

为了让我们的Filter能够发挥它的魔力,Spring需要加载我们的Config类。最后,我们需要确保我们的Servlet容器(即Tomcat)对每个请求都使用springSessionRepositoryFilter幸运的是,Spring Session提供了一个名为AbstractHttpSessionApplicationInitializer的实用程序类,这两个步骤都非常简单。你可以在下面找到一个例子:

的src /主/爪哇/样品/ Initializer.java
public class Initializer extends AbstractHttpSessionApplicationInitializer { (1)

	public Initializer() {
		super(Config.class); (2)
	}
}
我们班级的名字(初始化程序)并不重要。重要的是我们扩展AbstractHttpSessionApplicationInitializer
1 第一步是扩展AbstractHttpSessionApplicationInitializer这确保了名为springSessionRepositoryFilter的Spring Bean在每个请求中都注册了我们的Servlet容器。
2 AbstractHttpSessionApplicationInitializer还提供了一种机制,可以轻松确保Spring加载我们的Config

5.3.2.基于JDBC XML的配置

本节介绍如何使用关系数据库使用基于XML的配置来支持HttpSession

所述的HttpSession JDBC XML示例提供了关于如何整合Spring Session和使用XML配置HttpSession工作示例。您可以在下面阅读集成的基本步骤,但是在与您自己的应用程序集成时,建议您遵循详细的HttpSession JDBC XML指南。
Spring XML配置

添加所需的依赖项后,我们可以创建Spring配置。Spring配置负责创建一个Servlet过滤器,用Spring Session支持的实现替换HttpSession实现。添加以下Spring配置:

的src /主/ web应用/ WEB-INF /弹簧/ session.xml
(1)
<context:annotation-config/>
<bean class="org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessionConfiguration"/>

(2)
<jdbc:embedded-database id="dataSource" database-name="testdb" type="H2">
	<jdbc:script location="classpath:org/springframework/session/jdbc/schema-h2.sql"/>
</jdbc:embedded-database>

(3)
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<constructor-arg ref="dataSource"/>
</bean>
1 我们使用<context:annotation-config/>JdbcHttpSessionConfiguration的组合,因为Spring Session尚未提供XML命名空间支持(请参阅gh-104)。这将创建一个名为springSessionRepositoryFilter的Spring Bean,它实现了Filter。过滤器负责替换Spring Session支持的HttpSession实现。在这种情况下,Spring Session由关系数据库支持。
2 我们创建了一个dataSource,它将Spring Session连接到H2数据库的嵌入式实例。我们将H2数据库配置为使用Spring Session中包含的SQL脚本创建数据库表。
3 我们创建了一个transactionManager来管理先前配置的dataSource的事务。

有关如何配置与数据访问相关的问题的其他信息,请参阅Spring Framework参考文档

XML Servlet容器初始化

我们的Spring配置创建了一个名为springSessionRepositoryFilter的Spring Bean,它实现了FilterspringSessionRepositoryFilter bean负责使用Spring Session支持的自定义实现替换HttpSession

为了让我们的Filter能够发挥其魔力,我们需要指示Spring加载我们的session.xml配置。我们使用以下配置执行此操作:

的src /主/ web应用/ WEB-INF / web。XML
<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>
		/WEB-INF/spring/*.xml
	</param-value>
</context-param>
<listener>
	<listener-class>
		org.springframework.web.context.ContextLoaderListener
	</listener-class>
</listener>

ContextLoaderListener的读取contextConfigLocation的,拿起我们的session.xml配置。

最后,我们需要确保我们的Servlet容器(即Tomcat)对每个请求都使用springSessionRepositoryFilter以下代码段为我们执行了最后一步:

的src /主/ web应用/ WEB-INF / web。XML
<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>
	<dispatcher>REQUEST</dispatcher>
	<dispatcher>ERROR</dispatcher>
</filter-mapping>

DelegatingFilterProxy的将查找一个Bean由springSessionRepositoryFilter的名称,并将其转换为Filter对于调用DelegatingFilterProxy的每个请求,将调用springSessionRepositoryFilter

5.3.3.基于JDBC Spring Boot的配置

本节介绍在使用Spring Boot时如何使用关系数据库支持HttpSession

所述的HttpSession JDBC Spring Boot样品提供了关于如何整合Spring Session和HttpSession的Spring Boot工作示例。您可以在下面阅读集成的基本步骤,但是在与您自己的应用程序集成时,建议您遵循详细的HttpSession JDBC Spring Boot指南。
Spring Boot配置

添加所需的依赖项后,我们可以创建Spring Boot配置。由于支持一流的自动配置,设置由关系数据库支持的Spring Session就像向application.properties添加单个配置属性一样简单:

SRC /主/资源/ application.properties
spring.session.store-type = jdbc#会话存储类型。

在幕后,Spring Boot将应用相当于手动添加@EnableJdbcHttpSession注释的配置。这将创建一个名为springSessionRepositoryFilter的Spring Bean,它实现了Filter。过滤器负责替换Spring Session支持的HttpSession实现。

使用application.properties可以进一步定制:

SRC /主/资源/ application.properties
server.servlet.session.timeout =#会话超时。如果未指定持续时间后缀,则将使用秒。
spring.session.jdbc.initialize-schema = embedded#数据库模式初始化模式。
spring.session.jdbc.schema = classpath中:组织/ springframework的/会话/ JDBC / schema- @ @ 平台@ @ .SQL#的路径SQL文件,以用于初始化数据库架构。
spring.session.jdbc.table-name = SPRING_SESSION#用于存储会话的数据库表的名称。

有关更多信息,请参阅Spring Boot文档的Spring Session部分。

配置DataSource

Spring Boot自动创建DataSource,将Spring Session连接到H2数据库的嵌入式实例。在生产环境中,您需要确保更新配置以指向关系数据库。例如,您可以在application.properties中包含以下内容

SRC /主/资源/ application.properties
spring.datasource.url =#JDBC数据库的URL。
spring.datasource.username =#登录数据库的用户名。
spring.datasource.password =#数据库的登录密码。

有关更多信息,请参阅Spring Boot文档的配置数据源部分。

Servlet容器初始化

我们的Spring Boot配置创建了一个名为springSessionRepositoryFilter的Spring Bean,它实现了FilterspringSessionRepositoryFilter bean负责使用Spring Session支持的自定义实现替换HttpSession

为了让我们的Filter能够发挥它的魔力,Spring需要加载我们的Config类。最后,我们需要确保我们的Servlet容器(即Tomcat)对每个请求都使用springSessionRepositoryFilter幸运的是,Spring Boot为我们处理了这两个步骤。

5.4.HttpSession与Hazelcast

通过在使用HttpSession的任何内容之前添加Servlet过滤器来启用Spring Session和HttpSession

本节介绍如何使用Hazelcast使用基于Java的配置返回HttpSession

Hazelcast Spring样品提供了关于如何整合Spring Session,并使用Java配置HttpSession工作示例。您可以在下面阅读集成的基本步骤,但建议您在与自己的应用程序集成时遵循详细的Hazelcast Spring指南。

5.4.1.Spring配置

添加所需的依赖项后,我们可以创建Spring配置。Spring配置负责创建一个Servlet过滤器,用Spring Session支持的实现替换HttpSession实现。添加以下Spring配置:

@EnableHazelcastHttpSession (1)
@Configuration
public class HazelcastHttpSessionConfig {

	@Bean
	public HazelcastInstance hazelcastInstance() {
		MapAttributeConfig attributeConfig = new MapAttributeConfig()
				.setName(HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
				.setExtractor(PrincipalNameExtractor.class.getName());

		Config config = new Config();

		config.getMapConfig(HazelcastSessionRepository.DEFAULT_SESSION_MAP_NAME) (2)
				.addMapAttributeConfig(attributeConfig)
				.addMapIndexConfig(new MapIndexConfig(
						HazelcastSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));

		return Hazelcast.newHazelcastInstance(config); (3)
	}

}
1 @EnableHazelcastHttpSession注释创建一个名为springSessionRepositoryFilter的Spring Bean,它实现了Filter。过滤器负责替换Spring Session支持的HttpSession实现。在这种情况下,Spring Session由Hazelcast支持。
2 为了支持按主体名称索引检索会话,需要注册适当的ValueExtractorSpring Session为此目的提供了PrincipalNameExtractor
3 我们创建了HazelcastInstance,将Spring Session连接到Hazelcast。默认情况下,应用程序启动并连接Hazelcast的嵌入式实例。有关配置Hazelcast的更多信息,请参阅参考文档

5.4.2.Servlet容器初始化

我们的Spring配置创建了一个名为springSessionRepositoryFilter的Spring Bean,它实现了FilterspringSessionRepositoryFilter bean负责使用Spring Session支持的自定义实现替换HttpSession

为了使我们的Filter能够发挥其魔力,Spring需要加载我们的SessionConfig类。由于我们的应用程序已经使用我们的SecurityInitializer类加载Spring配置,我们可以简单地添加我们的SessionConfig类。

的src /主/爪哇/样品/ SecurityInitializer.java
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {

	public SecurityInitializer() {
		super(SecurityConfig.class, SessionConfig.class);
	}
}

最后,我们需要确保我们的Servlet容器(即Tomcat)对每个请求使用springSessionRepositoryFilter在Spring Security springSecurityFilterChain之前调用Spring Session的springSessionRepositoryFilter非常重要。这可确保Spring Security使用的HttpSession由Spring Session支持。幸运的是,Spring Session提供了一个名为AbstractHttpSessionApplicationInitializer的实用程序类,这使得这非常容易。你可以在下面找到一个例子:

的src /主/爪哇/样品/ Initializer.java
public class Initializer extends AbstractHttpSessionApplicationInitializer {

}
我们班级的名字(初始化程序)并不重要。重要的是我们延长AbstractHttpSessionApplicationInitializer

通过扩展AbstractHttpSessionApplicationInitializer,我们确保在Spring Security的springSecurityFilterChain之前,我们的Servlet容器中注册名为springSessionRepositoryFilter的Spring Bean。

5.5.HttpSession集成如何工作

幸运的是,HttpSessionHttpServletRequest(获得HttpSession的API)都是接口。这意味着我们可以为每个API提供我们自己的实现。

本节介绍Spring Session如何与HttpSession进行透明集成。目的是让用户能够理解幕后发生的事情。此功能已集成,您无需自己实现此逻辑。

首先,我们创建一个自定义HttpServletRequest,返回HttpSession的自定义实现。它看起来像下面这样:

public class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {

	public SessionRepositoryRequestWrapper(HttpServletRequest original) {
		super(original);
	}

	public HttpSession getSession() {
		return getSession(true);
	}

	public HttpSession getSession(boolean createNew) {
		// create an HttpSession implementation from Spring Session
	}

	// ... other methods delegate to the original HttpServletRequest ...
}

任何返回HttpSession的方法都会被覆盖。所有其他方法都由HttpServletRequestWrapper实现,并简单地委托给原始的HttpServletRequest实现。

我们使用名为SessionRepositoryFilter的servlet Filter替换HttpServletRequest实现。伪代码可以在下面找到:

public class SessionRepositoryFilter implements Filter {

	public doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
		HttpServletRequest httpRequest = (HttpServletRequest) request;
		SessionRepositoryRequestWrapper customRequest =
			new SessionRepositoryRequestWrapper(httpRequest);

		chain.doFilter(customRequest, response, chain);
	}

	// ...
}

通过将自定义HttpServletRequest实现传递到FilterChain,我们确保在Filter之后调用的任何内容都使用自定义HttpSession实现。这突出了为什么Spring Session的SessionRepositoryFilter必须放在与HttpSession交互的任何内容之前。

5.6.HttpSession和RESTful API

Spring Session可以通过允许在标头中提供会话来使用RESTful API。

REST样品提供了关于如何使用Spring Session在一个REST应用以支持与头认证工作样品。您可以按照以下集成的基本步骤进行操作,但建议您在与自己的应用程序集成时遵循详细的REST指南。

5.6.1.Spring配置

添加所需的依赖项后,我们可以创建Spring配置。Spring配置负责创建一个Servlet过滤器,用Spring Session支持的实现替换HttpSession实现。添加以下Spring配置:

@Configuration
@EnableRedisHttpSession (1)
public class HttpSessionConfig {

	@Bean
	public LettuceConnectionFactory connectionFactory() {
		return new LettuceConnectionFactory(); (2)
	}

	@Bean
	public HttpSessionIdResolver httpSessionIdResolver() {
		return HeaderHttpSessionIdResolver.xAuthToken(); (3)
	}
}
1 @EnableRedisHttpSession注释创建一个Spring Bean,其名称为springSessionRepositoryFilter,实现Filter过滤器负责替换Spring Session支持的HttpSession实现。在这种情况下,Spring Session由Redis支持。
2 我们创建一个RedisConnectionFactory,将Spring Session连接到Redis服务器。我们将连接配置为在默认端口上连接到localhost(6379)有关配置Spring Data Redis的更多信息,请参阅参考文档
3 我们自定义Spring Session的HttpSession集成以使用HTTP标头来传达当前会话信息而不是cookie。

5.6.2.Servlet容器初始化

我们的Spring配置创建了一个名为springSessionRepositoryFilter的Spring Bean,它实现了FilterspringSessionRepositoryFilter bean负责将HttpSession替换为Spring Session支持的自定义实现。

为了使我们的Filter能够发挥其魔力,Spring需要加载我们的Config类。我们在Spring MvcInitializer中提供配置,如下所示:

的src /主/爪哇/样品/ MVC / MvcInitializer.java
@Override
protected Class<?>[] getRootConfigClasses() {
	return new Class[] { SecurityConfig.class, HttpSessionConfig.class };
}

最后,我们需要确保我们的Servlet容器(即Tomcat)对每个请求都使用springSessionRepositoryFilter幸运的是,Spring Session提供了一个名为AbstractHttpSessionApplicationInitializer的实用程序类,这使得这非常容易。只需使用默认构造函数扩展类,如下所示:

的src /主/爪哇/样品/ Initializer.java
public class Initializer extends AbstractHttpSessionApplicationInitializer {

}
我们班级的名字(初始化程序)并不重要。重要的是我们延长AbstractHttpSessionApplicationInitializer

5.7.HttpSessionListener

Spring Session通过声明SessionEventHttpSessionListenerAdapterSessionDestroyedEventSessionCreatedEvent翻译成HttpSessionEvent来支持HttpSessionListener要使用此支持,您需要:

  • 确保您的SessionRepository实施支持并配置为触发SessionDestroyedEventSessionCreatedEvent

  • SessionEventHttpSessionListenerAdapter配置为Spring bean。

  • HttpSessionListener注入SessionEventHttpSessionListenerAdapter

如果您正在使用HttpSession中使用Redis记录的配置支持,那么您需要做的就是将每个HttpSessionListener注册为bean。例如,假设您想要支持Spring Security的并发控制,并且需要使用HttpSessionEventPublisher,您只需添加HttpSessionEventPublisher作为bean即可。在Java配置中,这可能如下所示:

@Configuration
@EnableRedisHttpSession
public class RedisHttpSessionConfig {

	@Bean
	public HttpSessionEventPublisher httpSessionEventPublisher() {
		return new HttpSessionEventPublisher();
	}

	// ...
}

在XML配置中,这可能如下所示:

<bean class="org.springframework.security.web.session.HttpSessionEventPublisher"/>

6. WebSocket集成

Spring Session提供与Spring的WebSocket支持的透明集成。

Spring Session的WebSocket支持仅适用于Spring的WebSocket支持。具体来说,它不能直接使用JSR-356这是因为JSR-356没有拦截传入的WebSocket消息的机制。

6.1.为什么Spring Session和WebSockets?

那么为什么我们在使用WebSockets时需要Spring Session?

考虑一个通过HTTP请求完成大部分工作的电子邮件应用程序。但是,还有一个嵌入在其中的聊天应用程序,可以在WebSocket API上运行。如果用户正在积极地与某人聊天,我们不应该超时HttpSession,因为这将是非常糟糕的用户体验。但是,这正是JSR-356所做的。

另一个问题是,根据JSR-356,如果使用该HttpSession和经过身份验证的用户创建的任何WebSocket的HttpSession超时,则应强行关闭。这意味着如果我们在我们的应用程序中积极聊天并且没有使用HttpSession,那么我们也将断开与我们的对话!

6.2.WebSocket用法

所述的WebSocket样品提供了关于如何整合Spring Session使用WebSockets工作示例。您可以按照以下集成的基本步骤进行操作,但建议您在与自己的应用程序集成时遵循详细的WebSocket指南:

6.2.1.HttpSession集成

在使用WebSocket集成之前,您应该确保首先使用HttpSession Integration

6.2.2.Spring配置

在典型的Spring WebSocket应用程序中,用户将实现WebSocketMessageBrokerConfigurer例如,配置可能如下所示:

@Configuration
@EnableScheduling
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

	@Override
	public void registerStompEndpoints(StompEndpointRegistry registry) {
		registry.addEndpoint("/messages").withSockJS();
	}

	@Override
	public void configureMessageBroker(MessageBrokerRegistry registry) {
		registry.enableSimpleBroker("/queue/", "/topic/");
		registry.setApplicationDestinationPrefixes("/app");
	}
}

我们可以轻松更新配置以使用Spring Session的WebSocket支持。例如:

的src /主/爪哇/样品/配置/ WebSocketConfig.java
@Configuration
@EnableScheduling
@EnableWebSocketMessageBroker
public class WebSocketConfig
		extends AbstractSessionWebSocketMessageBrokerConfigurer<Session> { (1)

	@Override
	protected void configureStompEndpoints(StompEndpointRegistry registry) { (2)
		registry.addEndpoint("/messages").withSockJS();
	}

	@Override
	public void configureMessageBroker(MessageBrokerRegistry registry) {
		registry.enableSimpleBroker("/queue/", "/topic/");
		registry.setApplicationDestinationPrefixes("/app");
	}
}

要加入Spring Session支持,我们只需要改变两件事:

1 我们没有实施WebSocketMessageBrokerConfigurer而是扩展AbstractSessionWebSocketMessageBrokerConfigurer
2 我们将registerStompEndpoints方法重命名为configureStompEndpoints

AbstractSessionWebSocketMessageBrokerConfigurer在幕后做了什么?

  • WebSocketConnectHandlerDecoratorFactory作为WebSocketHandlerDecoratorFactory添加到WebSocketTransportRegistration这可确保触发包含WebSocketSession的自定义SessionConnectEventWebSocketSession是终止Spring Session终止时仍然打开的任何WebSocket连接所必需的。

  • SessionRepositoryMessageInterceptor作为HandshakeInterceptor添加到每个StompWebSocketEndpointRegistration这可确保将Session添加到WebSocket属性中,以便更新上次访问的时间。

  • SessionRepositoryMessageInterceptor作为ChannelInterceptor添加到我们的入站ChannelRegistration这确保了每次收到入站消息时,我们Spring Session的最后访问时间都会更新。

  • WebSocketRegistryListener创建为Spring Bean。这确保了我们将所有Session id映射到相应的WebSocket连接。通过维护此映射,我们可以在Spring Session(HttpSession)终止时关闭所有WebSocket连接。

7. WebSession集成

Spring Session提供与Spring WebFlux WebSession的透明集成。这意味着开发人员可以使用Spring Session支持的实现来切换WebSession实现。

7.1.为什么Spring Session和WebSession?

我们已经提到Spring Session提供了与Spring WebFlux WebSession的透明集成,但我们从中获得了哪些好处?HttpSession一样,Spring Session使得支持群集会话变得微不足道,而不依赖于特定于应用程序容器的解决方案。

7.2.WebSession与Redis

只需注册Spring Session ReactiveSessionRepository支持的WebSessionManager实现,即可使用Spring Session和WebSessionSpring配置负责创建WebSessionManager,用Spring Session支持的实现替换WebSession实现。添加以下Spring配置:

@EnableRedisWebSession (1)
public class SessionConfiguration {

	@Bean
	public LettuceConnectionFactory redisConnectionFactory() {
		return new LettuceConnectionFactory(); (2)
	}

}
1 @EnableRedisWebSession注释创建一个Spring Bean,其名称为webSessionManager,实现WebSessionManager这是负责替换Spring Session支持的WebSession实现的原因。在这种情况下,Spring Session由Redis支持。
2 我们创建一个RedisConnectionFactory,将Spring Session连接到Redis服务器。我们将连接配置为在默认端口上连接到localhost(6379)有关配置Spring Data Redis的更多信息,请参阅参考文档

7.3.WebSession集成的工作原理

使用Spring WebFlux和WebSession Spring Session进行集成相比,与Servlet API和HttpSession相比,事情要简单得多。Spring WebFlux提供WebSessionStore API,提出了持久化WebSession的策略。

本节介绍Spring Session如何与WebSession进行透明集成。目的是让用户能够理解幕后发生的事情。此功能已集成,您无需自己实现此逻辑。

首先,我们创建一个委托给Spring Session Session的自定义SpringSessionWebSession它看起来像下面这样:

public class SpringSessionWebSession implements WebSession {

	enum State {
		NEW, STARTED
	}

	private final S session;

	private AtomicReference<State> state = new AtomicReference<>();

	SpringSessionWebSession(S session, State state) {
		this.session = session;
		this.state.set(state);
	}

	@Override
	public void start() {
		this.state.compareAndSet(State.NEW, State.STARTED);
	}

	@Override
	public boolean isStarted() {
		State value = this.state.get();
		return (State.STARTED.equals(value)
				|| (State.NEW.equals(value) && !this.session.getAttributes().isEmpty()));
	}

	@Override
	public Mono<Void> changeSessionId() {
		return Mono.defer(() -> {
			this.session.changeSessionId();
			return save();
		});
	}

	// ... other methods delegate to the original Session
}

接下来,我们创建一个自定义WebSessionStore,委托给ReactiveSessionRepository并将Session包装到自定义WebSession实现中:

public class SpringSessionWebSessionStore<S extends Session> implements WebSessionStore {

	private final ReactiveSessionRepository<S> sessions;

	public SpringSessionWebSessionStore(ReactiveSessionRepository<S> reactiveSessionRepository) {
		this.sessions = reactiveSessionRepository;
	}

	// ...
}

为了被Spring WebFlux检测到,这个自定义WebSessionStore需要在ApplicationContext中注册为名为webSessionManager的bean。有关Spring WebFlux的其他信息,请参阅Spring Framework参考文档

8. Spring Security整合

Spring Session提供与Spring Security的整合。

8.1.Spring Security记住我的支持

Spring Session提供了与Spring Security的Remember-Me身份验证的集成支持将:

  • 更改会话到期时长

  • 确保会话cookie在Integer.MAX_VALUE到期。Cookie到期时间设置为最大可能值,因为cookie仅在创建会话时设置。如果将其设置为与会话到期时相同的值,则会在用户使用会话时续订会话,但不会更新cookie到期,从而导致到期时间被修复。

要在Java配置中使用Spring Security配置Spring Session,请使用以下指南:

@Override
protected void configure(HttpSecurity http) throws Exception {
	http
		// ... additional configuration ...
		.rememberMe()
			.rememberMeServices(rememberMeServices());
}

@Bean
public SpringSessionRememberMeServices rememberMeServices() {
	SpringSessionRememberMeServices rememberMeServices =
			new SpringSessionRememberMeServices();
	// optionally customize
	rememberMeServices.setAlwaysRemember(true);
	return rememberMeServices;
}

基于XML的配置看起来像这样:

<security:http>
	<!-- ... -->
	<security:form-login />
	<security:remember-me services-ref="rememberMeServices"/>
</security:http>

<bean id="rememberMeServices"
	class="org.springframework.session.security.web.authentication.SpringSessionRememberMeServices"
	p:alwaysRemember="true"/>

8.2.Spring Security并发会话控制

Spring Session提供与Spring Security的集成以支持其并发会话控制。这允许限制单个用户可以同时拥有的活动会话数,但与默认的Spring Security支持不同,这也适用于集群环境。这是通过提供Spring Security的SessionRegistry接口的自定义实现来完成的。

使用Spring Security的Java配置DSL时,您可以通过SessionManagementConfigurer配置自定义SessionRegistry,如下所示:

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

	@Autowired
	private FindByIndexNameSessionRepository<Session> sessionRepository;

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		// @formatter:off
		http
			// other config goes here...
			.sessionManagement()
				.maximumSessions(2)
				.sessionRegistry(sessionRegistry());
		// @formatter:on
	}

	@Bean
	SpringSessionBackedSessionRegistry sessionRegistry() {
		return new SpringSessionBackedSessionRegistry<>(this.sessionRepository);
	}
}

这假设您还配置了Spring Session以提供返回Session个实例的FindByIndexNameSessionRepository

使用XML配置时,它看起来像这样:

<security:http>
	<!-- other config goes here... -->
	<security:session-management>
		<security:concurrency-control max-sessions="2" session-registry-ref="sessionRegistry"/>
	</security:session-management>
</security:http>

<bean id="sessionRegistry"
	  class="org.springframework.session.security.SpringSessionBackedSessionRegistry">
	<constructor-arg ref="sessionRepository"/>
</bean>

这假定您的Spring Session SessionRegistry bean被称为sessionRegistry,这是所有SpringHttpSessionConfiguration子类使用的名称。

8.3.限制

Spring Session Spring Security的SessionRegistry接口的实现不支持getAllPrincipals方法,因为使用Spring Session无法检索此信息。Spring Security从不调用此方法,因此这仅影响访问SessionRegistry本身的应用程序。

9. API文档

您可以在线浏览完整的Javadoc关键API如下所述:

9.1.会议

Session是名称值对的简化Map

典型用法可能如下所示:

public class RepositoryDemo<S extends Session> {
	private SessionRepository<S> repository; (1)

	public void demo() {
		S toSave = this.repository.createSession(); (2)

		(3)
		User rwinch = new User("rwinch");
		toSave.setAttribute(ATTR_USER, rwinch);

		this.repository.save(toSave); (4)

		S session = this.repository.findById(toSave.getId()); (5)

		(6)
		User user = session.getAttribute(ATTR_USER);
		assertThat(user).isEqualTo(rwinch);
	}

	// ... setter methods ...
}
1 我们使用泛型类型S创建SessionRepository实例,扩展Session泛型类型在我们的类中定义。
2 我们使用SessionRepository创建一个新的Session并将其分配给S类型的变量。
3 我们与Session互动。在我们的示例中,我们演示了将User保存到Session
4 我们现在保存Session这就是我们需要通用类型S的原因。SessionRepository仅允许保存使用相同SessionRepository创建或检索的Session个实例。这允许SessionRepository进行特定于实现的优化(即仅编写已更改的属性)。
5 我们从SessionRepository检索Session
6 我们从Session获取持久的User,而无需显式转换我们的属性。

Session API还提供与Session实例到期相关的属性。

典型用法可能如下所示:

public class ExpiringRepositoryDemo<S extends Session> {
	private SessionRepository<S> repository; (1)

	public void demo() {
		S toSave = this.repository.createSession(); (2)
		// ...
		toSave.setMaxInactiveInterval(Duration.ofSeconds(30)); (3)

		this.repository.save(toSave); (4)

		S session = this.repository.findById(toSave.getId()); (5)
		// ...
	}

	// ... setter methods ...
}
1 我们使用泛型类型S创建SessionRepository实例,扩展Session泛型类型在我们的类中定义。
2 我们使用SessionRepository创建一个新的Session并将其分配给S类型的变量。
3 我们与Session互动。在我们的示例中,我们演示了更新Session在其到期之前可以处于非活动状态的时间。
4 我们现在保存Session这就是我们需要通用类型S的原因。SessionRepository仅允许保存使用相同SessionRepository创建或检索的Session个实例。这允许SessionRepository进行特定于实现的优化(即仅编写已更改的属性)。保存Session时,上次访问的时间会自动更新。
5 我们从SessionRepository中检索Session如果Session已过期,则结果为null。

9.2.SessionRepository

SessionRepository负责创建,检索和保留Session个实例。

如果可能,开发人员不应直接与SessionRepositorySession进行交互。相反,开发人员应该更喜欢通过HttpSessionWebSocket集成间接与SessionRepositorySession进行交互

9.3.FindByIndexNameSessionRepository

Spring Session使用Session的最基本API是SessionRepository此API有意非常简单,因此很容易提供具有基本功能的其他实现。

一些SessionRepository实现也可以选择实现FindByIndexNameSessionRepository例如,Spring的Redis,JDBC和Hazelcast支持所有实现FindByIndexNameSessionRepository

FindByIndexNameSessionRepository提供了一种查找具有给定索引名称和索引值的所有会话的方法。作为所有提供的FindByIndexNameSessionRepository实现支持的常见用例,有一种方便的方法来查找特定用户的所有会话。这是通过确保使用用户名填充名称为FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME的会话属性来完成的。开发人员有责任确保填充属性,因为Spring Session不知道正在使用的身份验证机制。下面是一个如何使用它的示例:

String username = "username";
this.session.setAttribute(
		FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username);

FindByIndexNameSessionRepository的某些实现将提供钩子以自动索引其他会话属性。例如,许多实现将自动确保使用索引名FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME索引当前Spring Security用户名。

会话编入索引后,可以使用以下内容找到它:

String username = "username";
Map<String, Session> sessionIdToSession = this.sessionRepository
		.findByPrincipalName(username);

9.4.ReactiveSessionRepository

ReactiveSessionRepository负责以非阻塞和被动方式创建,检索和持久化Session实例。

如果可能,开发人员不应直接与ReactiveSessionRepositorySession进行交互。相反,开发人员应该更喜欢通过WebSession集成间接与ReactiveSessionRepositorySession进行交互

9.5.EnableSpringHttpSession

可以将@EnableSpringHttpSession注释添加到@Configuration类,以将SessionRepositoryFilter公开为名为“springSessionRepositoryFilter”的bean。为了利用注释,必须提供单个SessionRepository bean。例如:

@EnableSpringHttpSession
@Configuration
public class SpringHttpSessionConfig {
	@Bean
	public MapSessionRepository sessionRepository() {
		return new MapSessionRepository(new ConcurrentHashMap<>());
	}
}

请务必注意,没有为您配置会话到期的基础结构。这是因为会话到期之类的事情高度依赖于实现。这意味着如果您需要清理过期的会话,则您有责任清理过期的会话。

9.6.EnableSpringWebSession

可以将@EnableSpringWebSession注释添加到@Configuration类,以将WebSessionManager公开为名为“webSessionManager”的bean。为了利用注释,必须提供单个ReactiveSessionRepository bean。例如:

@EnableSpringWebSession
public class SpringWebSessionConfig {
	@Bean
	public ReactiveSessionRepository reactiveSessionRepository() {
		return new ReactiveMapSessionRepository(new ConcurrentHashMap<>());
	}
}

请务必注意,没有为您配置会话到期的基础结构。这是因为会话到期之类的事情高度依赖于实现。这意味着如果您需要清理过期的会话,则您有责任清理过期的会话。

9.7.RedisOperationsSessionRepository

RedisOperationsSessionRepository是使用Spring Data的RedisOperations实现的SessionRepository在web环境中,这通常与SessionRepositoryFilter结合使用。该实现支持SessionDestroyedEventSessionCreatedEventSessionMessageListener

9.7.1.实例化RedisOperationsSessionRepository

下面是一个如何创建新实例的典型示例:

RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();

// ... configure redisTemplate ...

SessionRepository<? extends Session> repository =
		new RedisOperationsSessionRepository(redisTemplate);

有关如何创建RedisConnectionFactory的其他信息,请参阅Spring Data Redis参考。

9.7.2.EnableRedisHttpSession

在web环境中,创建新RedisOperationsSessionRepository的最简单方法是使用@EnableRedisHttpSession可以在“ 示例和指南”(从此处开始)中找到完整的示例用法。 您可以使用以下属性来自定义配置:

  • maxInactiveIntervalInSeconds - 会话过期前的时间(以秒为单位)

  • redisNamespace - 允许为会话配置特定于应用程序的命名空间。Redis密钥和通道ID将以<redisNamespace>:的前缀开头。

  • redisFlushMode - 允许指定何时将数据写入Redis。默认值仅在SessionRepository上调用save时。RedisFlushMode.IMMEDIATE将尽快写入Redis。

自定义RedisSerializer

您可以通过创建实现RedisSerializer<Object>的名为springSessionDefaultRedisSerializer的Bean来自定义序列化。

9.7.3.Redis TaskExecutor

RedisOperationsSessionRepository订阅了使用RedisMessageListenerContainer从redis接收事件。您可以通过创建名为springSessionRedisTaskExecutor和/或Bean springSessionRedisSubscriptionExecutor的Bean来自定义调度这些事件的方式。有关配置redis任务执行程序的更多详细信息,请参见此处

9.7.4.存储细节

以下部分概述了如何针对每个操作更新Redis。可以在下面找到创建新会话的示例。后续部分描述了详细信息。

HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 \
	maxInactiveInterval 1800 \
	lastAccessedTime 1404360000000 \
	sessionAttr:attrName someAttrValue \
	sessionAttr2:attrName someAttrValue2
EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100
APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe“”
EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
SADD spring:session:expirations:1439245080000 expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe
EXPIRE spring:session:expirations1439245080000 2100
保存会话

每个会话都作为哈希存储在Redis中。使用HMSET命令设置和更新每个会话。下面将介绍如何存储每个会话的示例。

HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 \
	maxInactiveInterval 1800 \
	lastAccessedTime 1404360000000 \
	sessionAttr:attrName someAttrValue \
	sessionAttr2:attrName someAttrValue2

在此示例中,会话后续语句对于会话是真实的:

  • 会话ID为33fdd1b6-b496-4b33-9f7d-df96679d32fe

  • 自格林威治标准时间1970年1月1日午夜起,该会议创建于1404360000000英寸。

  • 会议将在1800秒(30分钟)后到期。

  • 该会议最后一次访问时间为1404360000000,自1970年1月1日格林威治标准时间午夜起以毫秒为单位。

  • 该会话有两个属性。第一个是“attrName”,其值为“someAttrValue”。第二个会话属性名为“attrName2”,其值为“someAttrValue2”。

优化的写作

RedisOperationsSessionRepository管理的Session实例会跟踪已更改的属性,并仅更新这些属性。这意味着如果一个属性被写入一次并且多次读取,我们只需要写一次该属性。例如,假设更新了之前的会话属性“sessionAttr2”。保存后将执行以下操作:

HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe sessionAttr:attrName2 newValue
会话到期

使用基于Session.getMaxInactiveInterval()的EXPIRE命令将到期与每个会话相关联。例如:

EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100

您将注意到,设置的到期时间是会话实际到期后的5分钟。这是必要的,以便在会话到期时可以访问会话的值。会话本身在实际到期后五分钟设置到期,以确保它被清理,但仅在我们执行任何必要的处理之后。

SessionRepository.findById(String)方法确保不会返回过期的会话。这意味着在使用会话之前无需检查到期日期。

Spring Session依赖于Redis 的删除和过期密钥空间通知分别触发SessionDeletedEventSessionExpiredEventSessionDeletedEventSessionExpiredEvent确保清除与会话相关的资源。例如,当使用Spring Session的WebSocket支持时,Redis过期或删除事件会触发与会话关联的任何WebSocket连接。

不会直接在会话密钥本身上跟踪到期,因为这意味着会话数据将不再可用。而是使用特殊会话到期密钥。在我们的示例中,expires键是:

APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe“”
EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800

当会话到期密钥被删除或过期时,密钥空间通知会触发查找实际会话并触发SessionDestroyedEvent。

依赖于Redis过期的一个问题是,如果未访问密钥,Redis不保证何时将触发过期事件。具体而言,Redis用于清除过期密钥的后台任务是低优先级任务,可能不会触发密钥过期。有关其他详细信息,请参阅Redis文档中的过期事件的时间部分。

为了避免过期事件无法保证发生这一事实,我们可以确保在预期到期时访问每个密钥。这意味着如果密钥上的TTL过期,Redis将在我们尝试访问密钥时删除密钥并触发过期事件。

因此,每个会话到期时间也会跟踪到最近的分钟。这允许后台任务访问可能过期的会话,以确保以更确定的方式触发Redis过期事件。例如:

SADD spring:session:expirations:1439245080000 expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe
EXPIRE spring:session:expirations1439245080000 2100

然后,后台任务将使用这些映射来明确请求每个密钥。通过访问密钥而不是删除密钥,我们确保只有在TTL过期时Redis才会删除密钥。

我们没有明确删除密钥,因为在某些情况下可能存在竞争条件错误地将密钥标识为过期而未过期。如果没有使用分布式锁(这会破坏我们的性能),就无法确保到期映射的一致性。通过简单地访问密钥,我们确保仅在该密钥上的TTL过期时才删除密钥。

9.7.5.SessionDeletedEvent和SessionExpiredEvent

SessionDeletedEventSessionExpiredEvent都是SessionDestroyedEvent的类型。

RedisOperationsSessionRepository支持在Session删除时触发SessionDeletedEvent或在SessionExpiredEvent到期时触发SessionExpiredEvent这对于确保正确清理与Session相关的资源是必要的。

例如,当与WebSockets集成时,SessionDestroyedEvent负责关闭任何活动的WebSocket连接。

通过SessionMessageListener收听SessionDeletedEventSessionExpiredEvent,听取Redis Keyspace事件为了实现这一点,需要启用针对通用命令和过期事件的Redis Keyspace事件。例如:

redis-cli config set notify-keyspace-events Egx

如果您使用@EnableRedisHttpSession SessionMessageListener并启用必要的Redis Keyspace事件,则会自动完成。但是,在安全的Redis环境中,config命令被禁用。这意味着Spring Session无法为您配置Redis Keyspace事件。要禁用自动配置,请将ConfigureRedisAction.NO_OP添加为bean。

例如,Java Configuration可以使用以下内容:

@Bean
public static ConfigureRedisAction configureRedisAction() {
	return ConfigureRedisAction.NO_OP;
}

XML配置可以使用以下内容:

<util:constant
	static-field="org.springframework.session.data.redis.config.ConfigureRedisAction.NO_OP"/>

9.7.6.SessionCreatedEvent

创建会话时,会使用spring:session:channel:created:33fdd1b6-b496-4b33-9f7d-df96679d32fe的通道向Redis发送事件,以使33fdd1b6-b496-4b33-9f7d-df96679d32fe成为会话ID。事件的主体将是创建的会话。

如果注册为MessageListener(默认),则RedisOperationsSessionRepository将Redis消息转换为SessionCreatedEvent

9.7.7.在Redis中查看会话

安装Redis的-CLI,您可以在Redis的检查值,使用Redis的-CLI例如,在终端中输入以下内容:

$ redis-cli
redis 127.0.0.1:6379> keys *
1) "spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021" (1)
2) "spring:session:expirations:1418772300000" (2)
1 该密钥的后缀是Spring Session的会话标识符。
2 此密钥包含应在1418772300000时删除的所有会话ID。

您还可以查看每个会话的属性。

redis 127.0.0.1:6379> hkeys spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021
1) "lastAccessedTime"
2) "creationTime"
3) "maxInactiveInterval"
4) "sessionAttr:username"
redis 127.0.0.1:6379> hget spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021 sessionAttr:username
"\xac\xed\x00\x05t\x00\x03rob"

9.8.ReactiveRedisOperationsSessionRepository

ReactiveRedisOperationsSessionRepository是使用Spring Data的ReactiveRedisOperations实现的ReactiveSessionRepository在web环境中,这通常与WebSessionStore结合使用。

9.8.1.实例化ReactiveRedisOperationsSessionRepository

下面是一个如何创建新实例的典型示例:

// ... create and configure connectionFactory and serializationContext ...

ReactiveRedisTemplate<String, Object> redisTemplate = new ReactiveRedisTemplate<>(
		connectionFactory, serializationContext);

ReactiveSessionRepository<? extends Session> repository =
		new ReactiveRedisOperationsSessionRepository(redisTemplate);

有关如何创建ReactiveRedisConnectionFactory的其他信息,请参阅Spring Data Redis参考。

9.8.2.EnableRedisWebSession

在web环境中,创建新ReactiveRedisOperationsSessionRepository的最简单方法是使用@EnableRedisWebSession您可以使用以下属性来自定义配置:

  • maxInactiveIntervalInSeconds - 会话过期前的时间(以秒为单位)

  • redisNamespace - 允许为会话配置特定于应用程序的命名空间。Redis密钥和通道ID将以<redisNamespace>:的前缀开头。

  • redisFlushMode - 允许指定何时将数据写入Redis。默认值仅在ReactiveSessionRepository上调用save时。RedisFlushMode.IMMEDIATE将尽快写入Redis。

优化的写作

ReactiveRedisOperationsSessionRepository管理的Session实例会跟踪已更改的属性,并仅更新这些属性。这意味着如果一个属性被写入一次并且多次读取,我们只需要写一次该属性。

9.8.3.在Redis中查看会话

安装Redis的-CLI,您可以在Redis的检查值,使用Redis的-CLI例如,在终端中输入以下内容:

$ redis-cli
redis 127.0.0.1:6379> keys *
1) "spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021" (1)
1 该密钥的后缀是Spring Session的会话标识符。

您还可以查看每个会话的属性。

redis 127.0.0.1:6379> hkeys spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021
1) "lastAccessedTime"
2) "creationTime"
3) "maxInactiveInterval"
4) "sessionAttr:username"
redis 127.0.0.1:6379> hget spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021 sessionAttr:username
"\xac\xed\x00\x05t\x00\x03rob"

9.9.MapSessionRepository

MapSessionRepository允许在Map中持久保存Session,其中密钥为Session ID,值为Session该实现可以与ConcurrentHashMap一起用作测试或便利机制。或者,它可以与分布式Map实现一起使用。例如,它可以与Hazelcast一起使用。

9.9.1.实例化MapSessionRepository

创建新实例非常简单:

SessionRepository<? extends Session> repository = new MapSessionRepository(
		new ConcurrentHashMap<>());

9.9.2.使用Spring Session和Hazlecast

Hazelcast样品是使用Spring Session含Hazelcast一个完整的应用程序演示。

要运行它,请使用以下命令:

./gradlew:samples:hazelcast:tomcatRun

Hazelcast Spring样品是一个完整的应用程序中使用Spring Session与{证明78 /}和Spring Security。

它包括支持触发SessionCreatedEventSessionDeletedEventSessionExpiredEvent的示例Hazelcast MapListener实现。

要运行它,请使用以下命令:

./gradlew:samples:hazelcast-spring:tomcatRun

9.10.ReactiveMapSessionRepository

ReactiveMapSessionRepository允许在Map中持久保存Session,其中密钥为Session ID,值为Session该实现可以与ConcurrentHashMap一起用作测试或便利机制。或者,它可以与分布式Map实现一起使用,并要求所提供的Map必须是非阻塞的。

9.11.JdbcOperationsSessionRepository

JdbcOperationsSessionRepositorySessionRepository实现,它使用Spring的JdbcOperations在关系数据库中存储会话。在web环境中,这通常与SessionRepositoryFilter结合使用。请注意,此实现不支持发布会话事件。

9.11.1.实例化JdbcOperationsSessionRepository

下面是一个如何创建新实例的典型示例:

JdbcTemplate jdbcTemplate = new JdbcTemplate();

// ... configure JdbcTemplate ...

PlatformTransactionManager transactionManager = new DataSourceTransactionManager();

// ... configure transactionManager ...

SessionRepository<? extends Session> repository =
		new JdbcOperationsSessionRepository(jdbcTemplate, transactionManager);

有关如何创建和配置JdbcTemplatePlatformTransactionManager的其他信息,请参阅Spring Framework参考文档

9.11.2.EnableJdbcHttpSession

在web环境中,创建新JdbcOperationsSessionRepository的最简单方法是使用@EnableJdbcHttpSession可以在“ 示例和指南”(从此处开始)中找到完整的示例用法。 您可以使用以下属性来自定义配置:

  • tableName - Spring Session用于存储会话的数据库表的名称

  • maxInactiveIntervalInSeconds - 会话过期前的时间(以秒为单位)

自定义LobHandler

您可以通过创建实现LobHandler的名为springSessionLobHandler的Bean来自定义BLOB处理。

自定义ConversionService

您可以通过提供ConversionService实例来自定义会话的默认序列化和反序列化。在典型的Spring环境中工作时,将自动选取默认的ConversionService Bean(名为conversionService)并用于序列化和反序列化。但是,您可以通过提供名为springSessionConversionService的Bean来覆盖默认值ConversionService

9.11.3.存储细节

默认情况下,此实现使用SPRING_SESSIONSPRING_SESSION_ATTRIBUTES表来存储会话。请注意,可以如上所述轻松自定义表名。在这种情况下,用于存储属性的表将使用提供的表名命名,后缀为_ATTRIBUTES如果需要进一步的自定义,可以使用set*Query setter方法自定义存储库使用的SQL查询。在这种情况下,您需要手动配置sessionRepository bean。

由于各种数据库供应商之间的差异,特别是在存储二进制数据时,请确保使用特定于数据库的SQL脚本。大多数主要数据库供应商的脚本打包为org/springframework/session/jdbc/schema-*.sql,其中*是目标数据库类型。

例如,使用PostgreSQL数据库,您将使用以下模式脚本:

CREATE TABLE SPRING_SESSION (
	PRIMARY_ID CHAR(36) NOT NULL,
	SESSION_ID CHAR(36) NOT NULL,
	CREATION_TIME BIGINT NOT NULL,
	LAST_ACCESS_TIME BIGINT NOT NULL,
	MAX_INACTIVE_INTERVAL INT NOT NULL,
	EXPIRY_TIME BIGINT NOT NULL,
	PRINCIPAL_NAME VARCHAR(100),
	CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
);

CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);

CREATE TABLE SPRING_SESSION_ATTRIBUTES (
	SESSION_PRIMARY_ID CHAR(36) NOT NULL,
	ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
	ATTRIBUTE_BYTES BYTEA NOT NULL,
	CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
	CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
);

并使用MySQL数据库:

CREATE TABLE SPRING_SESSION (
	PRIMARY_ID CHAR(36) NOT NULL,
	SESSION_ID CHAR(36) NOT NULL,
	CREATION_TIME BIGINT NOT NULL,
	LAST_ACCESS_TIME BIGINT NOT NULL,
	MAX_INACTIVE_INTERVAL INT NOT NULL,
	EXPIRY_TIME BIGINT NOT NULL,
	PRINCIPAL_NAME VARCHAR(100),
	CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;

CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);

CREATE TABLE SPRING_SESSION_ATTRIBUTES (
	SESSION_PRIMARY_ID CHAR(36) NOT NULL,
	ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
	ATTRIBUTE_BYTES BLOB NOT NULL,
	CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
	CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;

9.11.4.交易管理

JdbcOperationsSessionRepository中的所有JDBC操作都以事务方式执行。在传播设置为REQUIRES_NEW的情况下执行事务,以避免由于干扰现有事务而导致的意外行为(例如,在已经参与只读事务的线程中执行save操作)。

9.12.HazelcastSessionRepository

HazelcastSessionRepositorySessionRepository实现,以Hazelcast分发的IMap存储会话。在web环境中,这通常与SessionRepositoryFilter结合使用。

9.12.1.实例化HazelcastSessionRepository

下面是一个如何创建新实例的典型示例:

Config config = new Config();

// ... configure Hazelcast ...

HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(config);

HazelcastSessionRepository repository =
		new HazelcastSessionRepository(hazelcastInstance);

有关如何创建和配置Hazelcast实例的其他信息,请参阅Hazelcast文档

9.12.2.EnableHazelcastHttpSession

如果您希望使用Hazelcast作为SessionRepository的后备源,则可以将@EnableHazelcastHttpSession注释添加到@Configuration类。这扩展了@EnableSpringHttpSession注释提供的功能,但在Hazelcast中为你创建了SessionRepository您必须提供单个HazelcastInstance bean才能使配置生效。完整的配置示例可以在样本和指南中找到(从这里开始)

9.12.3.基本定制

您可以在@EnableHazelcastHttpSession上使用以下属性来自定义配置:

  • maxInactiveIntervalInSeconds - 会话过期前的时间(以秒为单位)。默认为1800秒(30分钟)

  • sessionMapName - 将在Hazelcast中用于存储会话数据的分布式Map的名称。

9.12.4.会议Events

使用MapListener来响应从分布式Map添加,逐出和删除的条目,这些事件将分别使用SessionCreatedEventSessionExpiredEventSessionDeletedEvent事件触发发布。

9.12.5.存储细节

会话将存储在Hazelcast中的分布式IMap中。IMap接口方法将用于get()put()会话。此外,values()方法用于支持FindByIndexNameSessionRepository#findByIndexNameAndIndexValue操作,以及需要在Hazelcast注册的适当ValueExtractor有关此配置的更多详细信息,请参阅Hazelcast Spring示例IMap中会话的到期由Hazelcast支持在put()进入IMap时设置条目的生存时间来处理。闲置时间超过生存时间的条目(会话)将自动从IMap中删除。

您不需要为Hazelcast配置中的IMap配置任何设置,例如max-idle-secondstime-to-live-seconds

请注意,如果您使用Hazelcast的MapStore来保持会话IMap,则从MapStore重新加载会话时会有一些限制:

  • 重新加载触发器EntryAddedListener,导致重新发布SessionCreatedEvent

  • reload使用给定IMap的默认TTL,这会导致会话丢失其原始TTL

10.自定义SessionRepository

实现自定义SessionRepositoryAPI应该是一项相当简单的任务。将自定义实现与@EnableSpringHttpSession支持相结合,可以轻松地重用现有的Spring Session配置工具和基础架构。然而,有几个方面需要进一步考虑。

在HTTP请求的生命周期中,HttpSession通常会持久保存到SessionRepository两次。首先,一旦客户端有权访问会话ID,就确保客户端可以使用该会话,并且在提交会话之后还必须进行写入,因为可能会对会话进行进一步的修改。考虑到这一点,通常建议SessionRepository实现跟踪更改以确保仅保存增量。这在高度并发的环境中尤为重要,其中多个请求在相同的HttpSession上运行并因此导致竞争条件,其中请求覆盖彼此改变会话属性。Spring Session提供的所有SessionRepository实现都使用所描述的方法来持久化会话更改,并可在实现自定义SessionRepository时用于指导。

请注意,相同的建议也适用于实现自定义ReactiveSessionRepository当然,在这种情况下@EnableSpringWebSession应该使用。

11.升级到2.x.

在新的主要版本中,Spring Session团队借此机会进行了一些非被动的更改。这些变化的重点是改进和协调Spring Session的API,以及删除不推荐使用的组件。

11.1.基线更新

Spring Session 2.0需要Java 8和Spring Framework 5.0作为基线,因为它的整个代码库现在基于Java 8源代码。有关升级Spring Framework的参考,请参阅升级到Spring Framework 5.x的指南

11.2.替换和删除模块

作为项目拆分模块的一部分,现有的spring-session已被spring-session-core模块取代。spring-session-core模块仅包含通用的API和组件集,而其他模块包含适当的SessionRepository实现以及与该数据存储相关的功能。这适用于以前是一个简单的依赖聚合器辅助模块的几个现有的,但新的模块安排实际上带有实现:

  • Spring Session数据Redis

  • Spring Session JDBC

  • Spring Session Hazelcast

此外,还从主项目存储库中删除了以下模块:

  • Spring Session数据MongoDB

  • Spring Session Data GemFire

请注意,这两个已移至单独的存储库,并将继续可用,尽管在更改的工件名称下:

11.3.替换和删除的包,类和方法

  • ExpiringSession API已合并到Session API中

  • Session API已得到增强,可充分利用Java 8

  • Session API已扩展为changeSessionId支持

  • SessionRepository API已更新,以更好地与Spring Data方法命名约定保持一致

  • AbstractSessionEvent及其子类在没有基础Session对象的情况下不再可构造

  • RedisOperationsSessionRepository使用的Redis命名空间现在是完全可配置的,而不是部分可配置的

  • Redis配置支持已更新,以避免注册Spring Session特定的RedisTemplate bean

  • JDBC配置支持已更新,以避免注册Spring Session特定的JdbcTemplate bean

  • 以前已弃用的类和方法已在代码库中删除

11.4.丢弃支持

作为对HttpSessionStrategy的更改的一部分,并且它与来自被动世界的对应方的一致,已删除了在单个浏览器实例中管理多个用户会话的支持。未来版本正在考虑引入新的API来替换此功能。

12. Spring Session社区

我们很高兴认为您是我们社区的一员。请在下面找到更多信息。

12.1.支持

您可以通过标签spring-sessionStackOverflow上提问来获得帮助同样,我们鼓励通过回答StackOverflow上的问题来帮助他人。

12.2.源代码

我们的源代码可以在GitHub上找到https://github.com/spring-projects/spring-session/

12.3.问题跟踪

12.4.特约

我们非常感谢Pull Requests

12.5.执照

Spring Session是在Apache 2.0许可下发布的开源软件

13.最低要求

Spring Session的最低要求是:

  • Java 8+

  • 如果您在Servlet容器(不是必需的)中运行,请使用Servlet 3.1+

  • 如果您使用的是其他Spring库(非必需),则所需的最低版本为Spring 5.0.x.

  • @EnableRedisHttpSession需要Redis 2.8+。这是支持会话过期所必需的

  • @EnableHazelcastHttpSession要求Hazelcast 3.6+。这是必要的支持FindByIndexNameSessionRepository

核心Spring Session仅对spring-jcl有必要的依赖。有关使用Spring Session而没有任何其他Spring依赖项的示例,请参阅hazelcast示例应用程序。