API 文档
您可以在线浏览完整的 Javadoc。关键 API 在以下部分中描述。
使用 Session
Session 是一个简化的键值对 Map。
典型用法可能如下所示
class RepositoryDemo<S extends Session> {
private SessionRepository<S> repository; (1)
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 实例,S 扩展了 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 实例过期相关的属性。
典型用法可能如下所示
class ExpiringRepositoryDemo<S extends Session> {
private SessionRepository<S> repository; (1)
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 实例,S 扩展了 Session。泛型类型在我们的类中定义。 |
| 2 | 我们使用 SessionRepository 创建一个新的 Session 并将其赋值给 S 类型的变量。 |
| 3 | 我们与 Session 交互。在我们的示例中,我们演示了更新 Session 在过期前可以不活动的持续时间。 |
| 4 | 我们现在保存 Session。这就是为什么我们需要泛型类型 S。SessionRepository 只允许保存使用相同 SessionRepository 创建或检索的 Session 实例。这允许 SessionRepository 进行特定于实现的优化(即只写入已更改的属性)。当 Session 保存时,上次访问时间会自动更新。 |
| 5 | 我们从 SessionRepository 中检索 Session。如果 Session 已过期,结果将为 null。 |
使用 SessionRepository
SessionRepository 负责创建、检索和持久化 Session 实例。
如果可能,您不应直接与 SessionRepository 或 Session 交互。相反,开发人员应优先通过 HttpSession 和 WebSocket 集成间接与 SessionRepository 和 Session 交互。
使用 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 的实现提供了自动索引其他会话属性的钩子。例如,许多实现会自动确保当前的 Spring Security 用户名使用索引名称 FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME 进行索引。 |
会话索引后,您可以使用类似于以下代码的方式进行查找。
String username = "username";
Map<String, Session> sessionIdToSession = this.sessionRepository.findByPrincipalName(username);
使用 ReactiveSessionRepository
ReactiveSessionRepository 负责以非阻塞和反应式方式创建、检索和持久化 Session 实例。
如果可能,您不应直接与 ReactiveSessionRepository 或 Session 交互。相反,您应优先通过 WebSession 集成间接与 ReactiveSessionRepository 和 Session 交互。
使用 @EnableSpringHttpSession
您可以将 @EnableSpringHttpSession 注解添加到 @Configuration 类中,以将 SessionRepositoryFilter 作为名为 springSessionRepositoryFilter 的 bean 公开。为了使用该注解,您必须提供单个 SessionRepository bean。以下示例显示了如何操作。
@EnableSpringHttpSession
@Configuration
public class SpringHttpSessionConfig {
@Bean
public MapSessionRepository sessionRepository() {
return new MapSessionRepository(new ConcurrentHashMap<>());
}
}
请注意,没有为您配置会话过期基础设施。这是因为会话过期等功能高度依赖于实现。这意味着,如果您需要清理过期会话,则您有责任清理过期会话。
使用 @EnableSpringWebSession
您可以将 @EnableSpringWebSession 注解添加到 @Configuration 类中,以将 WebSessionManager 作为名为 webSessionManager 的 bean 公开。要使用该注解,您必须提供单个 ReactiveSessionRepository bean。以下示例显示了如何操作。
@Configuration(proxyBeanMethods = false)
@EnableSpringWebSession
public class SpringWebSessionConfig {
@Bean
public ReactiveSessionRepository reactiveSessionRepository() {
return new ReactiveMapSessionRepository(new ConcurrentHashMap<>());
}
}
请注意,没有为您配置会话过期基础设施。这是因为会话过期等功能高度依赖于实现。这意味着,如果您需要清理过期会话,则您有责任清理过期会话。
使用 RedisSessionRepository
RedisSessionRepository 是一个使用 Spring Data 的 RedisOperations 实现的 SessionRepository。在 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 * enableIndexingAndEvents:是否使用 RedisIndexedSessionRepository 而不是 RedisSessionRepository。默认值为 false。 * maxInactiveIntervalInSeconds:会话过期前的时间量,以秒为单位。 * redisNamespace:允许为会话配置应用程序特定的命名空间。Redis 键和通道 ID 以 <redisNamespace>: 为前缀。 * flushMode:允许指定何时将数据写入 Redis。默认值是仅在 SessionRepository 上调用 save 时写入。值为 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 Session 的会话标识符。 |
您还可以使用 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 是一个使用 Spring Data 的 RedisOperations 实现的 SessionRepository。在 Web 环境中,这通常与 SessionRepositoryFilter 结合使用。此实现通过 SessionMessageListener 支持 SessionDestroyedEvent 和 SessionCreatedEvent 的发布。
实例化 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。默认值是仅在
SessionRepository上调用save时写入。值为FlushMode.IMMEDIATE会尽快写入 Redis。
Redis TaskExecutor
RedisIndexedSessionRepository 通过使用 RedisMessageListenerContainer 订阅从 Redis 接收事件。您可以通过创建名为 springSessionRedisTaskExecutor 的 bean、springSessionRedisSubscriptionExecutor 的 bean 或两者来自定义这些事件的分发方式。有关配置 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
后续部分描述了详细信息。
保存会话
每个会话都作为 Hash 存储在 Redis 中。每个会话都使用 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(自 1970 年 1 月 1 日 GMT 午夜以来的毫秒数)。
-
会话在 1800 秒(30 分钟)后过期。
-
会话上次访问于 1404360000000(自 1970 年 1 月 1 日 GMT 午夜以来的毫秒数)。
-
会话有两个属性。第一个是
attrName,值为someAttrValue。第二个会话属性名为attrName2,值为someAttrValue2。
优化写入
由 RedisIndexedSessionRepository 管理的 Session 实例会跟踪已更改的属性,并且只更新这些属性。这意味着,如果一个属性写入一次并读取多次,我们只需要写入该属性一次。例如,假设前面的清单中的 attrName2 会话属性已更新。保存时将运行以下命令。
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 过期或删除事件会触发与会话关联的任何 WebSocket 连接关闭。
过期时间不是直接在会话键本身上跟踪的,因为这意味着会话数据将不再可用。相反,使用了特殊的会话过期键。在前面的示例中,过期键如下所示。
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 支持在 Session 被删除时触发 SessionDeletedEvent,或者在 Session 过期时触发 SessionExpiredEvent。这对于确保与 Session 关联的资源得到正确清理是必要的。
例如,与 WebSocket 集成时,SessionDestroyedEvent 负责关闭任何活动的 WebSocket 连接。
通过 SessionMessageListener 提供 SessionDeletedEvent 或 SessionExpiredEvent 的触发,该监听器监听 Redis 键空间事件。为了使其工作,需要启用 Redis 键空间事件用于通用命令和过期事件。以下示例显示了如何操作。
redis-cli config set notify-keyspace-events Egx
如果您使用 @EnableRedisHttpSession(enableIndexingAndEvents = true),则会自动管理 SessionMessageListener 并启用必要的 Redis 键空间事件。但是,在安全的 Redis 环境中,config 命令是禁用的。这意味着 Spring Session 无法为您配置 Redis 键空间事件。要禁用自动配置,请将 ConfigureRedisAction.NO_OP 作为 bean 添加。
例如,对于 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 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"
使用 ReactiveRedisSessionRepository
ReactiveRedisSessionRepository 是一个使用 Spring Data 的 ReactiveRedisOperations 实现的 ReactiveSessionRepository。在 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 以
<redisNamespace>:为前缀。 -
flushMode:允许指定何时将数据写入 Redis。默认值是仅在
ReactiveSessionRepository上调用save时写入。值为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 Session 的会话标识符。 |
您还可以使用 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 允许将会话持久化到 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 结合使用。
它包括示例 Hazelcast MapListener 实现,支持触发 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 方法自定义存储库使用的 SQL 查询。在这种情况下,您需要手动配置 sessionRepository bean。
由于各种数据库供应商之间的差异,尤其是在存储二进制数据方面,请务必使用特定于您的数据库的 SQL 脚本。大多数主要数据库供应商的脚本都打包为 org/springframework/session/jdbc/schema-*.sql,其中 * 是目标数据库类型。
例如,对于 PostgreSQL,您可以使用以下 schema 脚本。
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 注解提供的功能,但在 Hazelcast 中为您创建了 SessionRepository。您必须提供单个 HazelcastInstance bean 才能使配置生效。您可以在 示例和指南(从这里开始) 中找到完整的配置示例。
基本自定义
您可以使用 @EnableHazelcastHttpSession 上的以下属性来自定义配置。
-
maxInactiveIntervalInSeconds:会话过期前的时间量,以秒为单位。默认值为 1800 秒(30 分钟)。
-
sessionMapName:Hazelcast 中用于存储会话数据的分布式
Map的名称。
会话事件
使用 MapListener 响应添加到分布式 Map、被逐出和从分布式 Map 中删除的条目会导致这些事件通过 ApplicationEventPublisher 触发 SessionCreatedEvent、SessionExpiredEvent 和 SessionDeletedEvent 事件(分别)。
存储详情
会话存储在 Hazelcast 的分布式 IMap 中。IMap 接口方法用于 get() 和 put() 会话。此外,values() 方法支持 FindByIndexNameSessionRepository#findByIndexNameAndIndexValue 操作,以及适当的 ValueExtractor(需要注册到 Hazelcast)。有关此配置的更多详细信息,请参阅 Hazelcast Spring 示例。IMap 中会话的过期由 Hazelcast 对条目在 put() 到 IMap 时设置生存时间的支持来处理。闲置时间超过生存时间的条目(会话)将自动从 IMap 中删除。
您不需要在 Hazelcast 配置中为 IMap 配置任何设置,例如 max-idle-seconds 或 time-to-live-seconds。
请注意,如果您使用 Hazelcast 的 MapStore 来持久化您的会话 IMap,则从 MapStore 重新加载会话时会应用以下限制。
-
重新加载会触发
EntryAddedListener,导致SessionCreatedEvent重新发布。 -
重新加载会话时,如果使用给定
IMap的默认 TTL,则会导致会话失去其原始 TTL。
使用 CookieSerializer
CookieSerializer 负责定义会话 cookie 的写入方式。Spring Session 提供了使用 DefaultCookieSerializer 的默认实现。
将 CookieSerializer 作为 bean 暴露
当您使用 @EnableRedisHttpSession 等配置时,将 CookieSerializer 作为 Spring bean 暴露会增强现有配置。
以下示例展示了如何实现:
@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
您可以使用 DefaultCookieSerializer 上的以下任何配置选项来自定义会话 cookie 的写入方式。
-
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:SameSiteCookie 指令的值。要禁用SameSiteCookie 指令的序列化,您可以将此值设置为null。默认值:Lax
| 您应该只匹配有效的域名字符,因为域名会反映在响应中。这样做可以防止恶意用户执行诸如 HTTP 响应拆分之类的攻击。 |
自定义 SessionRepository
实现自定义 SessionRepository API 应该是一项相当直接的任务。将自定义实现与 @EnableSpringHttpSession 支持相结合,可以重用现有的 Spring Session 配置设施和基础设施。然而,有几个方面值得更密切的考虑。
在 HTTP 请求的生命周期中,HttpSession 通常会被持久化到 SessionRepository 两次。第一次持久化操作是为了确保客户端在访问会话 ID 后立即可以使用会话,并且在会话提交后也需要写入,因为可能会对会话进行进一步的修改。考虑到这一点,我们通常建议 SessionRepository 实现跟踪更改,以确保只保存增量。这在高度并发的环境中尤为重要,其中多个请求操作相同的 HttpSession,因此会导致竞争条件,请求会覆盖彼此对会话属性的更改。Spring Session 提供的所有 SessionRepository 实现都使用上述方法持久化会话更改,并可在您实现自定义 SessionRepository 时用作指导。
请注意,对于实现自定义 ReactiveSessionRepository,也适用相同的建议。在这种情况下,您应该使用 @EnableSpringWebSession。