对于最新的稳定版本,请使用 Spring Session 3.5.2! |
API 文档
您可以在线浏览完整的 Javadoc。以下部分介绍了关键 API:
用Session
一个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 | 我们创建一个SessionRepository 具有泛型类型的实例,S ,这会扩展Session .泛型类型在我们的类中定义。 |
2 | 我们创建一个新的Session 通过使用我们的SessionRepository 并将其赋值给类型为S . |
3 | 我们与Session .在我们的示例中,我们演示了保存User 到Session . |
4 | 我们现在保存Session .这就是我们需要泛型类型的原因S .这SessionRepository 只允许保存Session 使用相同的SessionRepository .这允许SessionRepository 进行特定于实现的优化(即,仅编写已更改的属性)。 |
5 | 我们检索Session 从SessionRepository . |
6 | 我们获得持久化的User 从我们的Session 无需显式强制转换我们的属性。 |
这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 | 我们创建一个SessionRepository 具有泛型类型的实例,S ,这会扩展Session .泛型类型在我们的类中定义。 |
2 | 我们创建一个新的Session 通过使用我们的SessionRepository 并将其赋值给类型为S . |
3 | 我们与Session .
在我们的示例中,我们演示了更新Session 在过期之前可以处于非活动状态。 |
4 | 我们现在保存Session .
这就是为什么我们需要泛型类型,S . 这SessionRepository 仅允许保存Session 使用相同的SessionRepository .
这允许SessionRepository 进行特定于实现的优化(即,仅编写已更改的属性)。
上次访问时间会自动更新Session 被保存了。 |
5 | 我们检索Session 从SessionRepository .
如果Session 已过期,则结果为 null。 |
用SessionRepository
一个SessionRepository
负责创建、检索和持久化Session
实例。
如果可能,您不应直接与SessionRepository
或Session
.
相反,开发人员应该更喜欢与SessionRepository
和Session
间接通过HttpSession
和 WebSocket 集成。
用FindByIndexNameSessionRepository
Spring Session 最基本的 API,用于使用Session
是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 提供钩子以自动索引其他会话属性。
例如,许多实现会自动确保当前 Spring Security 用户名使用索引名称FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME . |
为会话编制索引后,可以使用类似于以下内容的代码进行查找:
String username = "username";
Map<String, Session> sessionIdToSession = this.sessionRepository.findByPrincipalName(username);
用ReactiveSessionRepository
一个ReactiveSessionRepository
负责创建、检索和持久化Session
实例以非阻塞和响应的方式进行。
如果可能,您不应直接与ReactiveSessionRepository
或Session
.
相反,您应该更喜欢与ReactiveSessionRepository
和Session
间接通过 WebSession 集成。
用@EnableSpringHttpSession
您可以添加@EnableSpringHttpSession
注释到@Configuration
类来公开SessionRepositoryFilter
作为名为springSessionRepositoryFilter
.
为了使用注释,您必须提供单个SessionRepository
豆。
以下示例显示了如何执行此作:
@EnableSpringHttpSession
@Configuration
public class SpringHttpSessionConfig {
@Bean
public MapSessionRepository sessionRepository() {
return new MapSessionRepository(new ConcurrentHashMap<>());
}
}
请注意,没有为您配置会话过期的基础结构。 这是因为会话过期等内容高度依赖于实现。 这意味着,如果您需要清理过期的会话,您有责任清理过期的会话。
用@EnableSpringWebSession
您可以添加@EnableSpringWebSession
注释到@Configuration
类来公开WebSessionManager
作为名为webSessionManager
.
要使用注释,您必须提供单个ReactiveSessionRepository
豆。
以下示例显示了如何执行此作:
@Configuration(proxyBeanMethods = false)
@EnableSpringWebSession
public class SpringWebSessionConfig {
@Bean
public ReactiveSessionRepository reactiveSessionRepository() {
return new ReactiveMapSessionRepository(new ConcurrentHashMap<>());
}
}
请注意,没有为您配置会话过期的基础结构。 这是因为会话过期等内容高度依赖于实现。 这意味着,如果您需要清理过期的会话,则有责任清理过期的会话。
用RedisSessionRepository
RedisSessionRepository
是一个SessionRepository
这是通过使用 Spring Data 的RedisOperations
.
在 Web 环境中,这通常与SessionRepositoryFilter
.
请注意,此实现不支持发布会话事件。
实例化RedisSessionRepository
您可以在以下列表中看到如何创建新实例的典型示例:
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// ... configure redisTemplate ...
SessionRepository<? extends Session> repository = new RedisSessionRepository(redisTemplate);
有关如何创建RedisConnectionFactory
,请参阅 Spring Data Redis 参考。
用@EnableRedisHttpSession
在 Web 环境中,创建新的RedisSessionRepository
就是用@EnableRedisHttpSession
.
您可以在示例和指南(从此处开始)中找到完整的示例用法。
您可以使用以下属性来自定义配置:
启用索引和事件
* enableIndexingAndEvents:是否使用RedisIndexedSessionRepository
而不是RedisSessionRepository
.默认值为false
.
* maxInactiveIntervalInSeconds:会话到期前的时间量(以秒为单位)。
* redisNamespace:允许为会话配置特定于应用程序的命名空间。Redis 键和通道 ID 以前缀<redisNamespace>:
.
* flushMode:允许指定何时将数据写入 Redis。默认值仅在save
在SessionRepository
.
值FlushMode.IMMEDIATE
尽快写给 Redis。
在 Redis 中查看会话
安装 redis-cli 后,您可以使用 redis-cli 检查 Redis 中的值。例如,您可以在终端窗口中输入以下命令:
$ redis-cli
redis 127.0.0.1:6379> keys *
1) "spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021" (1)
1 | 此键的后缀是 Spring 会话的会话标识符。 |
您还可以使用hkeys
命令。 以下示例显示了如何执行此作:
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"
用RedisIndexedSessionRepository
RedisIndexedSessionRepository
是一个SessionRepository
这是通过使用 Spring Data 的RedisOperations
.
在 Web 环境中,这通常与SessionRepositoryFilter
. 实现支持SessionDestroyedEvent
和SessionCreatedEvent
通过SessionMessageListener
.
实例化RedisIndexedSessionRepository
您可以在以下列表中看到如何创建新实例的典型示例:
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// ... configure redisTemplate ...
SessionRepository<? extends Session> repository = new RedisIndexedSessionRepository(redisTemplate);
有关如何创建RedisConnectionFactory
,请参阅 Spring Data Redis 参考。
用@EnableRedisHttpSession(enableIndexingAndEvents = true)
在 Web 环境中,创建新的RedisIndexedSessionRepository
就是用@EnableRedisHttpSession(enableIndexingAndEvents = true)
.
您可以在示例和指南(从此处开始)中找到完整的示例用法。
您可以使用以下属性来自定义配置:
-
enableIndexingAndEvents:是否使用
RedisIndexedSessionRepository
而不是RedisSessionRepository
.默认值为false
. -
maxInactiveIntervalInSeconds:会话到期之前的时间量(以秒为单位)。
-
redisNamespace:允许为会话配置特定于应用程序的命名空间。Redis 键和通道 ID 以前缀
<redisNamespace>:
. -
flushMode:允许指定何时将数据写入 Redis。默认值仅在
save
在SessionRepository
. 值FlushMode.IMMEDIATE
尽快写给 Redis。
RedisTaskExecutor
RedisIndexedSessionRepository
订阅以使用RedisMessageListenerContainer
.
您可以通过创建名为springSessionRedisTaskExecutor
,一个豆子springSessionRedisSubscriptionExecutor
,或两者兼而有之。
您可以在此处找到有关配置 Redis 任务执行器的更多详细信息。
存储详细信息
以下部分概述了如何针对每个作更新 Redis。 以下示例显示了创建新会话的示例:
HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 \ maxInactiveInterval 1800 \ lastAccessedTime 1404360000000 \ sessionAttr:attrName someAttrValue \ sessionAttr:attrName2 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 中作为Hash
.
每个会话都通过使用HMSET
命令。
以下示例显示了每个会话的存储方式:
HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 \ maxInactiveInterval 1800 \ lastAccessedTime 1404360000000 \ sessionAttr:attrName someAttrValue \ sessionAttr:attrName2 someAttrValue2
在前面的示例中,以下语句适用于会话:
-
会话 ID 为 33fdd1b6-b496-4b33-9f7d-df96679d32fe。
-
会话是在 1404360000000 时创建的(自格林威治标准时间 1 年 1 月 1 日午夜以来的毫秒)。
-
会话将在 1800 秒(30 分钟)后过期。
-
该会话最后一次访问是在 1404360000000 点(自格林威治标准时间 1970 年 1 月 1 日午夜以来的毫秒)。
-
会话有两个属性。 首先是
attrName
,值为someAttrValue
. 第二个会话属性名为attrName2
,值为someAttrValue2
.
优化写入
这Session
由RedisIndexedSessionRepository
跟踪已更改的属性并仅更新这些属性。
这意味着,如果一个属性写入一次并读取多次,我们只需要写入该属性一次。
例如,假设attrName2
更新了上一节中 lsiting 中的 session 属性。
保存时将运行以下命令:
HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe sessionAttr:attrName2 newValue
会话过期
过期时间通过使用EXPIRE
命令,基于Session.getMaxInactiveInterval()
. 以下示例显示了典型的EXPIRE
命令:
EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100
请注意,设置为会话实际过期后五分钟的过期时间。 这是必要的,以便在会话到期时可以访问会话的值。 会话本身在实际过期五分钟后设置过期时间,以确保它被清理,但只有在我们执行任何必要的处理之后。
这SessionRepository.findById(String) 方法确保不会返回过期的会话。
这意味着在使用会话之前无需检查过期时间。 |
Spring Session 依赖于 Redis 的删除和过期密钥空间通知来触发SessionDeletedEvent
和SessionExpiredEvent
分别。SessionDeletedEvent
或SessionExpiredEvent
确保与Session
被清理干净。
例如,当您使用 Spring Session 的 WebSocket 支持时,Redis expired 或 delete 事件会触发与要关闭的会话关联的任何 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
然后,后台任务使用这些映射来显式请求每个键。通过访问密钥而不是删除它,我们确保 Redis 仅在 TTL 过期时才会为我们删除密钥。
我们不会显式删除密钥,因为在某些情况下,可能存在竞争条件,错误地将密钥识别为已过期,而密钥并未过期。如果不使用分布式锁(这会降低我们的性能),则无法确保过期映射的一致性。通过简单地访问密钥,我们确保仅当该密钥上的 TTL 过期时才会删除该密钥。 |
SessionDeletedEvent
和SessionExpiredEvent
SessionDeletedEvent
和SessionExpiredEvent
都是SessionDestroyedEvent
.
RedisIndexedSessionRepository
支持发射SessionDeletedEvent
当Session
被删除或SessionExpiredEvent
当Session
到期。 这对于确保与Session
被适当清理。
例如,与 WebSockets 集成时,SessionDestroyedEvent
负责关闭任何活动的 WebSocket 连接。
开火SessionDeletedEvent
或SessionExpiredEvent
可通过SessionMessageListener
,它监听 Redis Keyspace 事件。为了实现这一点,需要启用通用命令和过期事件的 Redis Keyspace 事件。以下示例显示了如何执行此作:
redis-cli config set notify-keyspace-events Egx
如果您使用@EnableRedisHttpSession(enableIndexingAndEvents = true)
,管理SessionMessageListener
并且启用必要的 Redis Keyspace 事件是自动完成的。但是,在安全的 Redis 环境中,config 命令被禁用。这意味着 Spring Session 无法为您配置 Redis Keyspace 事件。要禁用自动配置,请添加ConfigureRedisAction.NO_OP
作为豆子。
例如,对于 Java 配置,您可以使用以下内容:
@Bean
ConfigureRedisAction configureRedisAction() {
return ConfigureRedisAction.NO_OP;
}
在 XML 配置中,您可以使用以下内容:
<util:constant
static-field="org.springframework.session.data.redis.config.ConfigureRedisAction.NO_OP"/>
用SessionCreatedEvent
创建会话时,将向 Redis 发送一个事件,其通道 ID 为spring:session:channel:created:33fdd1b6-b496-4b33-9f7d-df96679d32fe
,
哪里33fdd1b6-b496-4b33-9f7d-df96679d32fe
是会话 ID。事件的正文是创建的会话。
如果注册为MessageListener
(默认值),RedisIndexedSessionRepository
然后将 Redis 消息转换为SessionCreatedEvent
.
在 Redis 中查看会话
安装 redis-cli 后,您可以使用 redis-cli 检查 Redis 中的值。 例如,您可以在终端中输入以下内容:
$ 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 会话的会话标识符。 |
2 | 此密钥包含当时应删除的所有会话 ID1418772300000 . |
您还可以查看每个会话的属性。 以下示例显示了如何执行此作:
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"
用ReactiveRedisSessionRepository
ReactiveRedisSessionRepository
是一个ReactiveSessionRepository
这是通过使用 Spring Data 的ReactiveRedisOperations
.
在 Web 环境中,这通常与WebSessionStore
.
实例化ReactiveRedisSessionRepository
以下示例演示如何创建新实例:
// ... create and configure connectionFactory and serializationContext ...
ReactiveRedisTemplate<String, Object> redisTemplate = new ReactiveRedisTemplate<>(connectionFactory,
serializationContext);
ReactiveSessionRepository<? extends Session> repository = new ReactiveRedisSessionRepository(redisTemplate);
有关如何创建ReactiveRedisConnectionFactory
,请参阅 Spring Data Redis 参考。
用@EnableRedisWebSession
在 Web 环境中,创建新的ReactiveRedisSessionRepository
就是用@EnableRedisWebSession
.
您可以使用以下属性来自定义配置:
-
maxInactiveIntervalInSeconds:会话到期前的时间量(以秒为单位)
-
redisNamespace:允许为会话配置特定于应用程序的命名空间。Redis 键和通道 ID 以 q 前缀
<redisNamespace>:
. -
flushMode:允许指定何时将数据写入 Redis。默认值仅在
save
在ReactiveSessionRepository
. 值FlushMode.IMMEDIATE
尽快写给 Redis。
在 Redis 中查看会话
安装 redis-cli 后,您可以使用 redis-cli 检查 Redis 中的值。例如,您可以在终端窗口中输入以下命令:
$ redis-cli
redis 127.0.0.1:6379> keys *
1) "spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021" (1)
1 | 此键的后缀是 Spring 会话的会话标识符。 |
您还可以使用hkeys
命令。 以下示例显示了如何执行此作:
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"
用MapSessionRepository
这MapSessionRepository
允许持久化Session
在Map
,关键是Session
ID 和值为Session
.
您可以将实现与ConcurrentHashMap
作为测试或便利机制。
或者,您可以将其与分布式Map
实现。例如,它可以与 Hazelcast 一起使用。
实例化MapSessionRepository
以下示例演示如何创建新实例:
SessionRepository<? extends Session> repository = new MapSessionRepository(new ConcurrentHashMap<>());
使用 Spring Session 和 Hazlecast
Hazelcast 示例是一个完整的应用程序,演示了如何将 Spring Session 与 Hazelcast 一起使用。
要运行它,请使用以下命令:
./gradlew :samples:hazelcast:tomcatRun
Hazelcast Spring 示例是一个完整的应用程序,演示了如何将 Spring Session 与 Hazelcast 和 Spring Security 一起使用。
它包括示例 HazelcastMapListener
支持触发的实现SessionCreatedEvent
,SessionDeletedEvent
和SessionExpiredEvent
.
要运行它,请使用以下命令:
./gradlew :samples:hazelcast-spring:tomcatRun
用ReactiveMapSessionRepository
这ReactiveMapSessionRepository
允许持久化Session
在Map
,关键是Session
ID 和值为Session
.
您可以将实现与ConcurrentHashMap
作为测试或便利机制。
或者,您可以将其与分布式Map
实现,并要求提供的Map
必须是非阻塞的。
用JdbcIndexedSessionRepository
JdbcIndexedSessionRepository
是一个SessionRepository
使用 Spring 的JdbcOperations
将会话存储在关系数据库中。
在 Web 环境中,这通常与SessionRepositoryFilter
.
请注意,此实现不支持发布会话事件。
实例化JdbcIndexedSessionRepository
以下示例演示如何创建新实例:
JdbcTemplate jdbcTemplate = new JdbcTemplate();
// ... configure jdbcTemplate ...
TransactionTemplate transactionTemplate = new TransactionTemplate();
// ... configure transactionTemplate ...
SessionRepository<? extends Session> repository = new JdbcIndexedSessionRepository(jdbcTemplate,
transactionTemplate);
有关如何创建和配置的其他信息JdbcTemplate
和PlatformTransactionManager
,请参阅 Spring Framework 参考文档。
用@EnableJdbcHttpSession
在 Web 环境中,创建新的JdbcIndexedSessionRepository
就是用@EnableJdbcHttpSession
.
您可以在示例和指南中找到完整的示例用法(从这里开始)您可以使用以下属性来自定义配置:
-
tableName:Spring Session用于存储会话的数据库表的名称
-
maxInactiveIntervalInSeconds:会话过期之前的时间量(以秒为单位)
存储详细信息
默认情况下,此实现使用SPRING_SESSION
和SPRING_SESSION_ATTRIBUTES
用于存储会话的表。
请注意,您可以自定义表名称,如前所述。在这种情况下,用于存储属性的表使用提供的表名命名,后缀为_ATTRIBUTES
.
如果需要进一步自定义,可以使用set*Query
setter 方法。在这种情况下,您需要手动配置sessionRepository
豆。
由于各种数据库提供商之间的差异,尤其是在存储二进制数据时,请确保使用特定于数据库的 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;
用HazelcastIndexedSessionRepository
HazelcastIndexedSessionRepository
是一个SessionRepository
将会话存储在 Hazelcast 的分布式IMap
.
在 Web 环境中,这通常与SessionRepositoryFilter
.
实例化HazelcastIndexedSessionRepository
以下示例演示如何创建新实例:
Config config = new Config();
// ... configure Hazelcast ...
HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(config);
HazelcastIndexedSessionRepository repository = new HazelcastIndexedSessionRepository(hazelcastInstance);
有关如何创建和配置 Hazelcast 实例的更多信息,请参阅 Hazelcast 文档。
用@EnableHazelcastHttpSession
要使用 Hazelcast 作为SessionRepository
,您可以添加@EnableHazelcastHttpSession
注释到@Configuration
类。
这样做可以扩展@EnableSpringHttpSession
注释,但使SessionRepository
在 Hazelcast 中为您服务。
您必须提供单个HazelcastInstance
bean 的配置。
您可以在示例和指南(从此处开始)中找到完整的配置示例。
基本定制
您可以在以下@EnableHazelcastHttpSession
要自定义配置,请执行以下作:
-
maxInactiveIntervalInSeconds:会话到期之前的时间量(以秒为单位)。默认值为 1800 秒(30 分钟)
-
sessionMapName:分布式
Map
在 Hazelcast 中用于存储会话数据。
会话事件
使用MapListener
响应从已分发的Map
导致这些事件触发发布SessionCreatedEvent
,SessionExpiredEvent
和SessionDeletedEvent
事件(分别)通过ApplicationEventPublisher
.
存储详细信息
会话存储在分布式IMap
在黑兹尔卡斯特。
这IMap
接口方法用于get()
和put()
会话。
此外,values()
方法支持FindByIndexNameSessionRepository#findByIndexNameAndIndexValue
作,以及适当的ValueExtractor
(需要在 Hazelcast 注册)。有关此配置的更多详细信息,请参阅 Hazelcast Spring 示例。
会话在IMap
由 Hazelcast 支持在条目上设置生存时间put()
进入IMap
.空闲时间超过生存时间的条目(会话)将自动从IMap
.
您不需要配置任何设置,例如max-idle-seconds
或time-to-live-seconds
对于IMap
在 Hazelcast 配置中。
请注意,如果您使用 Hazelcast 的MapStore
以持久化会话IMap
,则从MapStore
:
-
重新加载触发器
EntryAddedListener
结果SessionCreatedEvent
正在重新发布 -
重新加载对给定的 TTL 使用默认 TTL
IMap
导致会话丢失其原始 TTL
用CookieSerializer
一个CookieSerializer
负责定义会话 cookie 的编写方式。Spring Session 带有一个默认实现,使用DefaultCookieSerializer
.
暴露CookieSerializer
作为豆子
公开CookieSerializer
作为 Spring bean 在您使用@EnableRedisHttpSession
.
以下示例显示了如何执行此作:
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("JSESSIONID"); (1)
serializer.setCookiePath("/"); (2)
serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$"); (3)
return serializer;
}
1 | 我们将 cookie 的名称自定义为JSESSIONID . |
2 | 我们将 cookie 的路径自定义为 (而不是上下文根的默认值)。/ |
3 | 我们将域名模式(正则表达式)自定义为^.?\\.(\\w\\.[a-z]+)$ .
这允许跨域和应用程序共享会话。
如果正则表达式不匹配,则不设置域,使用现有域。
如果正则表达式匹配,则使用第一个分组作为域。
这意味着对 child.example.com 的请求会将域设置为example.com .
但是,对 localhost:8080/ 或 192.168.1.100:8080/ 的请求会使 cookie 保持未设置状态,因此,在开发中仍然可以工作,而无需对生产进行任何更改。 |
您应该只匹配有效的域字符,因为域名会反映在响应中。 这样做可以防止恶意用户执行 HTTP 响应拆分等攻击。 |
定制CookieSerializer
您可以使用以下任何配置选项来自定义会话 cookie 的写入方式DefaultCookieSerializer
.
-
cookieName
:要使用的 Cookie 的名称。 违约:SESSION
. -
useSecureCookie
:指定是否应使用安全 Cookie。 默认值:使用HttpServletRequest.isSecure()
在创作时。 -
cookiePath
:Cookie 的路径。 默认值:上下文根。 -
cookieMaxAge
:指定创建会话时要设置的 Cookie 的最大期限。 违约:-1
,这表示在浏览器关闭时应删除 cookie。 -
jvmRoute
:指定要附加到会话 ID 并包含在 Cookie 中的后缀。 用于标识要路由到哪个 JVM 以实现会话关联性。 对于某些实现(即 Redis),此选项不提供性能优势。 但是,它可以帮助跟踪特定用户的日志。 -
domainName
:允许指定要用于 cookie 的特定域名。 此选项易于理解,但通常需要在开发环境和生产环境之间进行不同的配置。 看domainNamePattern
作为替代方案。 -
domainNamePattern
:一种不区分大小写的模式,用于从HttpServletRequest#getServerName()
. 该模式应提供用于提取 cookie 域值的单个分组。 如果正则表达式不匹配,则不设置域,使用现有域。 如果正则表达式匹配,则使用第一个分组作为域。 -
sameSite
:的值SameSite
cookie 指令。 要禁用SameSite
cookie 指令,您可以将此值设置为null
. 违约:Lax
您应该只匹配有效的域字符,因为域名会反映在响应中。 这样做可以防止恶意用户执行 HTTP 响应拆分等攻击。 |
定制SessionRepository
实现自定义SessionRepository
API 应该是一项相当简单的任务。
将自定义实现与@EnableSpringHttpSession
支持允许您重用现有的 Spring Session 配置工具和基础设施。
然而,有几个方面值得仔细考虑。
在 HTTP 请求的生命周期内,HttpSession
通常持久化为SessionRepository
两次。
第一个持久化作是确保客户端在客户端可以访问会话 ID 后立即对客户端可用,并且还需要在提交会话后写入,因为可能会对会话进行进一步修改。
考虑到这一点,我们通常建议将SessionRepository
实现 跟踪更改以确保仅保存增量。
这在高度并发的环境中尤为重要,因为多个请求在同一个环境中运行HttpSession
因此,会导致竞争条件,请求会覆盖彼此对会话属性的更改。
所有SessionRepository
Spring Session 提供的实现使用所描述的方法来持久化会话更改,并且可以在实现自定义SessionRepository
.
请注意,相同的建议也适用于实现自定义ReactiveSessionRepository
也。
在这种情况下,您应该使用@EnableSpringWebSession
.