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 | 我们创建一个具有泛型类型 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。 |
使用 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
时才会写入 Redis。值为 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
时才会写入 Redis。值为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
后续部分描述了详细信息。
保存会话
每个会话都存储在 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(自 1970 年 1 月 1 日格林威治标准时间午夜以来的毫秒数)。
-
会话将在 1800 秒(30 分钟)后过期。
-
会话最后访问时间为 1404360000000(自 1970 年 1 月 1 日格林威治标准时间午夜以来的毫秒数)。
-
会话有两个属性。第一个是
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
关联的资源得到正确清理是必要的。
例如,在与 WebSockets 集成时,SessionDestroyedEvent
负责关闭任何活动的 WebSocket 连接。
通过 SessionMessageListener
提供触发 SessionDeletedEvent
或 SessionExpiredEvent
的功能,该监听器监听 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
添加为 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
是一个 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 以
<redisNamespace>:
的前缀 q 开头。 -
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
允许将 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。
它包含示例 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 框架参考文档。
使用 @EnableJdbcHttpSession
在 Web 环境中,创建新的 JdbcIndexedSessionRepository
的最简单方法是使用 @EnableJdbcHttpSession
。您可以在 示例和指南(从这里开始) 中找到完整的示例用法。您可以使用以下属性来定制配置
-
tableName: Spring Session 用于存储会话的数据库表的名称
-
maxInactiveIntervalInSeconds: 会话在秒数内过期之前的时间量
存储详细信息
默认情况下,此实现使用 SPRING_SESSION
和 SPRING_SESSION_ATTRIBUTES
表来存储会话。请注意,您可以自定义表名,如前所述。在这种情况下,用于存储属性的表名称将使用提供的表名后缀 _ATTRIBUTES
来命名。如果需要进一步自定义,您可以使用 set*Query
设置方法来自定义存储库使用的 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;
使用 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
的后端源,您可以在 @Configuration
类中添加 @EnableHazelcastHttpSession
注解。这样做扩展了 @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 公开
将 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
您可以使用 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
:SameSite
cookie 指令的值。要禁用SameSite
cookie 指令的序列化,可以将此值设置为null
。默认值:Lax
您应该只匹配有效的域名字符,因为域名会反映在响应中。这样做可以防止恶意用户执行诸如 HTTP 响应拆分 之类的攻击。 |
自定义 SessionRepository
实现自定义 SessionRepository
API 应该是一项相当简单的任务。将自定义实现与 @EnableSpringHttpSession
支持相结合,可以让你重用现有的 Spring Session 配置设施和基础设施。但是,有一些方面值得仔细考虑。
在 HTTP 请求的生命周期中,HttpSession
通常会两次持久化到 SessionRepository
。第一次持久化操作是为了确保会话在客户端访问会话 ID 时立即可用,并且在会话提交后也需要写入,因为可能会对会话进行进一步修改。考虑到这一点,我们通常建议 SessionRepository
实现跟踪更改,以确保只保存增量。这在高度并发环境中尤其重要,在该环境中,多个请求对同一个 HttpSession
进行操作,因此会导致竞争条件,请求会覆盖彼此对会话属性的更改。Spring Session 提供的所有 SessionRepository
实现都使用所描述的方法来持久化会话更改,并且可以在实现自定义 SessionRepository
时用作指导。
请注意,相同的建议也适用于实现自定义 ReactiveSessionRepository
。在这种情况下,你应该使用 @EnableSpringWebSession
。