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.M1, 2.0.0.M2, 2.0.0.M3, 2.0.0.M4, 2.0.0.M5, 2.0.0.RC1, 2.0.0.RC2和 2.0.0.RELEASE。
-
升级到Java 8和Spring Framework 5作为基线
-
添加了对使用Redis
ReactiveSessionRepository
管理Spring WebFlux的WebSession
的支持 -
改进
Session
和SessionRepository
API -
为所有支持的会话存储提供改进和协调的配置支持
-
大量的性能改进和错误修复
3.样品和指南(从这里开始)
如果您希望开始使用Spring Session,最好的起点是我们的示例应用程序。
资源 | 描述 | 指南 |
---|---|---|
演示如何使用Spring Session将 |
||
演示如何使用Spring Session将 |
||
演示如何使用Spring Session按用户名查找会话。 |
||
演示如何将Spring Session与WebSockets一起使用。 |
||
演示如何使用Spring Session用Redis替换Spring WebFlux的 |
TBD |
|
演示如何使用Spring Session使用JSON序列化将Redis替换为 |
TBD |
资源 | 描述 | 指南 |
---|---|---|
演示如何使用Spring Session将 |
||
演示如何使用Spring Session将 |
||
演示如何使用Spring Session将 |
||
演示如何使用Spring Session并自定义cookie。 |
||
演示如何将Spring Session与现有的Spring Security应用程序一起使用。 |
||
演示如何在REST应用程序中使用Spring Session来支持使用标头进行身份验证。 |
资源 | 描述 | 指南 |
---|---|---|
演示如何使用Spring Session将 |
||
演示如何使用Spring Session将 |
资源 | 描述 | 指南 |
---|---|---|
演示如何将Spring Session与Grails 3一起使用。 |
||
演示如何在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 Core,Spring Session Data Redis,Spring Session JDBC和Spring Session Hazelcast模块
-
-
spring-session-data-mongodb
存储库-
主机Spring Session Data MongoDB模块
-
-
-
主机Spring Session数据Geode和Spring Session数据Geode模块
-
最后,Spring Session现在还提供Maven BOM(如“材料清单”)模块,以帮助用户解决版本管理问题:
-
-
主持Spring Session 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,它实现了Filter
。springSessionRepositoryFilter
bean负责使用Spring Session支持的自定义实现替换HttpSession
。
为了使我们的Filter
能够发挥其魔力,Spring需要加载我们的Config
类。最后,我们需要确保我们的Servlet容器(即Tomcat)对每个请求都使用springSessionRepositoryFilter
。幸运的是,Spring Session提供了一个名为AbstractHttpSessionApplicationInitializer
的实用程序类,这两个步骤都非常简单。你可以在下面找到一个例子:
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配置:
(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,它实现了Filter
。springSessionRepositoryFilter
bean负责使用Spring Session支持的自定义实现替换HttpSession
。
为了让我们的Filter
能够发挥其魔力,我们需要指示Spring加载我们的session.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
。以下代码段为我们执行了最后一步:
<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,它实现了Filter
。springSessionRepositoryFilter
bean负责使用Spring Session支持的自定义实现替换HttpSession
。
为了让我们的Filter
能够发挥它的魔力,Spring需要加载我们的Config
类。最后,我们需要确保我们的Servlet容器(即Tomcat)对每个请求都使用springSessionRepositoryFilter
。幸运的是,Spring Session提供了一个名为AbstractHttpSessionApplicationInitializer
的实用程序类,这两个步骤都非常简单。你可以在下面找到一个例子:
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配置:
(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,它实现了Filter
。springSessionRepositoryFilter
bean负责使用Spring Session支持的自定义实现替换HttpSession
。
为了让我们的Filter
能够发挥其魔力,我们需要指示Spring加载我们的session.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
。以下代码段为我们执行了最后一步:
<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
添加单个配置属性一样简单:
spring.session.store-type = jdbc#会话存储类型。
在幕后,Spring Boot将应用相当于手动添加@EnableJdbcHttpSession
注释的配置。这将创建一个名为springSessionRepositoryFilter
的Spring Bean,它实现了Filter。过滤器负责替换Spring Session支持的HttpSession
实现。
使用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中包含以下内容
spring.datasource.url =#JDBC数据库的URL。 spring.datasource.username =#登录数据库的用户名。 spring.datasource.password =#数据库的登录密码。
有关更多信息,请参阅Spring Boot文档的配置数据源部分。
Servlet容器初始化
我们的Spring Boot配置创建了一个名为springSessionRepositoryFilter
的Spring Bean,它实现了Filter
。springSessionRepositoryFilter
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 | 为了支持按主体名称索引检索会话,需要注册适当的ValueExtractor 。Spring Session为此目的提供了PrincipalNameExtractor 。 |
3 | 我们创建了HazelcastInstance ,将Spring Session连接到Hazelcast。默认情况下,应用程序启动并连接Hazelcast的嵌入式实例。有关配置Hazelcast的更多信息,请参阅参考文档。 |
5.4.2.Servlet容器初始化
我们的Spring配置创建了一个名为springSessionRepositoryFilter
的Spring Bean,它实现了Filter
。springSessionRepositoryFilter
bean负责使用Spring Session支持的自定义实现替换HttpSession
。
为了使我们的Filter
能够发挥其魔力,Spring需要加载我们的SessionConfig
类。由于我们的应用程序已经使用我们的SecurityInitializer
类加载Spring配置,我们可以简单地添加我们的SessionConfig
类。
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
的实用程序类,这使得这非常容易。你可以在下面找到一个例子:
public class Initializer extends AbstractHttpSessionApplicationInitializer {
}
我们班级的名字(初始化程序)并不重要。重要的是我们延长AbstractHttpSessionApplicationInitializer 。
|
通过扩展AbstractHttpSessionApplicationInitializer
,我们确保在Spring Security的springSecurityFilterChain
之前,我们的Servlet容器中注册名为springSessionRepositoryFilter
的Spring Bean。
5.5.HttpSession集成如何工作
幸运的是,HttpSession
和HttpServletRequest
(获得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,它实现了Filter
。springSessionRepositoryFilter
bean负责将HttpSession
替换为Spring Session支持的自定义实现。
为了使我们的Filter
能够发挥其魔力,Spring需要加载我们的Config
类。我们在Spring MvcInitializer
中提供配置,如下所示:
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { SecurityConfig.class, HttpSessionConfig.class };
}
最后,我们需要确保我们的Servlet容器(即Tomcat)对每个请求都使用springSessionRepositoryFilter
。幸运的是,Spring Session提供了一个名为AbstractHttpSessionApplicationInitializer
的实用程序类,这使得这非常容易。只需使用默认构造函数扩展类,如下所示:
public class Initializer extends AbstractHttpSessionApplicationInitializer {
}
我们班级的名字(初始化程序)并不重要。重要的是我们延长AbstractHttpSessionApplicationInitializer 。
|
5.7.HttpSessionListener
Spring Session通过声明SessionEventHttpSessionListenerAdapter
将SessionDestroyedEvent
和SessionCreatedEvent
翻译成HttpSessionEvent
来支持HttpSessionListener
。要使用此支持,您需要:
-
确保您的
SessionRepository
实施支持并配置为触发SessionDestroyedEvent
和SessionCreatedEvent
。 -
将
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支持。例如:
@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
的自定义SessionConnectEvent
。WebSocketSession
是终止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和WebSession
。Spring配置负责创建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
子类使用的名称。
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
个实例。
如果可能,开发人员不应直接与SessionRepository
或Session
进行交互。相反,开发人员应该更喜欢通过HttpSession和WebSocket集成间接与SessionRepository
和Session
进行交互。
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);
|
会话编入索引后,可以使用以下内容找到它:
String username = "username";
Map<String, Session> sessionIdToSession = this.sessionRepository
.findByPrincipalName(username);
9.4.ReactiveSessionRepository
ReactiveSessionRepository
负责以非阻塞和被动方式创建,检索和持久化Session
实例。
如果可能,开发人员不应直接与ReactiveSessionRepository
或Session
进行交互。相反,开发人员应该更喜欢通过WebSession集成间接与ReactiveSessionRepository
和Session
进行交互。
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
结合使用。该实现支持SessionDestroyedEvent
和SessionCreatedEvent
至SessionMessageListener
。
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。
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分钟。这是必要的,以便在会话到期时可以访问会话的值。会话本身在实际到期后五分钟设置到期,以确保它被清理,但仅在我们执行任何必要的处理之后。
|
Spring Session依赖于Redis 的删除和过期密钥空间通知,分别触发SessionDeletedEvent和SessionExpiredEvent。SessionDeletedEvent
或SessionExpiredEvent
确保清除与会话相关的资源。例如,当使用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
SessionDeletedEvent
和SessionExpiredEvent
都是SessionDestroyedEvent
的类型。
RedisOperationsSessionRepository
支持在Session
删除时触发SessionDeletedEvent
或在SessionExpiredEvent
到期时触发SessionExpiredEvent
。这对于确保正确清理与Session
相关的资源是必要的。
例如,当与WebSockets集成时,SessionDestroyedEvent
负责关闭任何活动的WebSocket连接。
通过SessionMessageListener
收听SessionDeletedEvent
或SessionExpiredEvent
,听取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。
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。
它包括支持触发SessionCreatedEvent
,SessionDeletedEvent
和SessionExpiredEvent
的示例Hazelcast MapListener
实现。
要运行它,请使用以下命令:
./gradlew:samples:hazelcast-spring:tomcatRun
9.10.ReactiveMapSessionRepository
ReactiveMapSessionRepository
允许在Map
中持久保存Session
,其中密钥为Session
ID,值为Session
。该实现可以与ConcurrentHashMap
一起用作测试或便利机制。或者,它可以与分布式Map
实现一起使用,并要求所提供的Map
必须是非阻塞的。
9.11.JdbcOperationsSessionRepository
JdbcOperationsSessionRepository
是SessionRepository
实现,它使用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);
有关如何创建和配置JdbcTemplate
和PlatformTransactionManager
的其他信息,请参阅Spring Framework参考文档。
9.11.2.EnableJdbcHttpSession
在web环境中,创建新JdbcOperationsSessionRepository
的最简单方法是使用@EnableJdbcHttpSession
。可以在“ 示例和指南”(从此处开始)中找到完整的示例用法。
您可以使用以下属性来自定义配置:
-
tableName - Spring Session用于存储会话的数据库表的名称
-
maxInactiveIntervalInSeconds - 会话过期前的时间(以秒为单位)
9.11.3.存储细节
默认情况下,此实现使用SPRING_SESSION
和SPRING_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.12.HazelcastSessionRepository
HazelcastSessionRepository
是SessionRepository
实现,以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
添加,逐出和删除的条目,这些事件将分别使用SessionCreatedEvent
,SessionExpiredEvent
和SessionDeletedEvent
事件触发发布。
9.12.5.存储细节
会话将存储在Hazelcast中的分布式IMap
中。IMap
接口方法将用于get()
和put()
会话。此外,values()
方法用于支持FindByIndexNameSessionRepository#findByIndexNameAndIndexValue
操作,以及需要在Hazelcast注册的适当ValueExtractor
。有关此配置的更多详细信息,请参阅Hazelcast Spring示例。IMap
中会话的到期由Hazelcast支持在put()
进入IMap
时设置条目的生存时间来处理。闲置时间超过生存时间的条目(会话)将自动从IMap
中删除。
您不需要为Hazelcast配置中的IMap
配置任何设置,例如max-idle-seconds
或time-to-live-seconds
。
请注意,如果您使用Hazelcast的MapStore
来保持会话IMap
,则从MapStore
重新加载会话时会有一些限制:
-
重新加载触发器
EntryAddedListener
,导致重新发布SessionCreatedEvent
-
reload使用给定
IMap
的默认TTL,这会导致会话丢失其原始TTL
10.自定义SessionRepository
实现自定义SessionRepository
API应该是一项相当简单的任务。将自定义实现与@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 -
以前已弃用的类和方法已在代码库中删除
12. Spring Session社区
我们很高兴认为您是我们社区的一员。请在下面找到更多信息。
12.1.支持
您可以通过标签spring-session在StackOverflow上提问来获得帮助。同样,我们鼓励通过回答StackOverflow上的问题来帮助他人。
12.2.源代码
我们的源代码可以在GitHub上找到https://github.com/spring-projects/spring-session/
12.3.问题跟踪
我们通过https://github.com/spring-projects/spring-session/issues跟踪GitHub问题。
12.4.特约
我们非常感谢Pull Requests。
12.5.执照
Spring Session是在Apache 2.0许可下发布的开源软件。
12.6.社区扩展
名称 | 地点 |
---|---|
Spring Session OrientDB |
|
Spring Session Infinispan |
http://infinispan.org/docs/dev/user_guide/user_guide.html#externalizing_session_using_spring_session |
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仅对 |