跳到主要内容
版本:3.4.2

API 文档

QWen Max 中英对照 API Documentation

您可以在线浏览完整的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 ...

}
  • 我们创建一个 SessionRepository 实例,其泛型 S 继承自 Session。泛型类型在我们的类中定义。

  • 我们使用 SessionRepository 创建一个新的 Session,并将其赋值给类型为 S 的变量。

  • 我们与 Session 进行交互。在示例中,我们演示了将 User 保存到 Session 中。

  • 现在我们保存 Session。这就是为什么我们需要泛型 S。SessionRepository 只允许保存通过相同 SessionRepository 创建或检索的 Session 实例。这使得 SessionRepository 能够进行特定于实现的优化(例如,仅写入已更改的属性)。

  • 我们从 SessionRepository 中检索 Session。

  • 我们从 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 ...

}
  • 我们创建一个 SessionRepository 实例,其泛型类型 S 继承自 Session。泛型类型在我们的类中定义。

  • 我们使用 SessionRepository 创建一个新的 Session,并将其赋值给类型为 S 的变量。

  • 我们与 Session 进行交互。在示例中,我们展示了更新 Session 在过期前可以处于非活动状态的时间。

  • 现在我们保存 Session。这就是为什么我们需要泛型类型 S。SessionRepository 只允许保存使用相同 SessionRepository 创建或检索的 Session 实例。这使得 SessionRepository 可以进行特定于实现的优化(例如,仅写入已更改的属性)。保存 Session 时会自动更新最后访问时间。

  • 我们从 SessionRepository 中检索 Session。如果 Session 已过期,结果将为空。

使用 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​

您可以在 @Configuration 类中添加 @EnableSpringHttpSession 注解,以将 SessionRepositoryFilter 作为名为 springSessionRepositoryFilter 的 bean 暴露出来。为了使用该注解,您必须提供一个 SessionRepository bean。以下示例展示了如何实现这一点:

@EnableSpringHttpSession
@Configuration
public class SpringHttpSessionConfig {

@Bean
public MapSessionRepository sessionRepository() {
return new MapSessionRepository(new ConcurrentHashMap<>());
}

}

请注意,没有为你配置会话过期的基础设施。这是因为诸如会话过期之类的事情高度依赖于具体实现。这意味着,如果你需要清理过期的会话,你需要负责清理这些过期的会话。

使用 @EnableSpringWebSession​

你可以在 @Configuration 类中添加 @EnableSpringWebSession 注解,以将 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。

自定义 RedisSerializer​

你可以通过创建一个名为 springSessionDefaultRedisSerializer 的 bean 来自定义序列化,该 bean 需要实现 RedisSerializer<Object>。

查看 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>
  • 该密钥的后缀是 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。

自定义 RedisSerializer​

你可以通过创建一个名为 springSessionDefaultRedisSerializer 的 bean 来自定义序列化,该 bean 需要实现 RedisSerializer<Object>。

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 的删除和过期keyspace notifications来分别触发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

当会话过期密钥被删除或过期时,keyspace 通知会触发对实际会话的查找,并触发一个 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 过期时才会被移除。

SessionDeletedEvent 和 SessionExpiredEvent​

SessionDeletedEvent 和 SessionExpiredEvent 都是 SessionDestroyedEvent 的类型。

RedisIndexedSessionRepository 支持在 Session 被删除时触发 SessionDeletedEvent,或在 Session 过期时触发 SessionExpiredEvent。这是为了确保与 Session 相关的资源能够被正确清理。

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

触发 SessionDeletedEvent 或 SessionExpiredEvent 是通过 SessionMessageListener 完成的,该监听器监听 Redis Keyspace 事件。为了使此功能正常工作,需要启用 Generic 命令和 Expired 事件的 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>
  • 该密钥的后缀是 Spring Session 的会话标识符。

  • 此密钥包含在 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。

优化的写操作​

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

查看 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>
  • 该密钥的后缀是 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 示例是一个完整的应用程序,它演示了如何使用 Hazelcast 的 Spring Session。

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

./gradlew :samples:hazelcast:tomcatRun

Hazelcast Spring 示例 是一个完整的应用程序,演示了如何将 Spring Session 与 Hazelcast 和 Spring Security 一起使用。

它包括支持触发 SessionCreatedEvent、SessionDeletedEvent 和 SessionExpiredEvent 的示例 Hazelcast MapListener 实现。

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

./gradlew :samples:hazelcast-spring:tomcatRun

使用 ReactiveMapSessionRepository​

ReactiveMapSessionRepository 允许将 Session 持久化到一个 Map 中,键是 Session ID,值是 Session。你可以使用 ConcurrentHashMap 的实现作为测试或便捷机制。或者,你也可以将其与分布式 Map 实现一起使用,但要求提供的 Map 必须是非阻塞的。

使用 JdbcIndexedSessionRepository​

JdbcIndexedSessionRepository 是一个 SessionRepository 实现,它使用 Spring 的 JdbcOperations 将会话存储在关系型数据库中。在网络环境中,这通常与 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: 用于存储会话的数据库表的名称

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

自定义 LobHandler​

你可以通过创建一个名为 springSessionLobHandler 的 bean 来自定义 BLOB 处理,该 bean 需要实现 LobHandler 接口。

自定义 ConversionService​

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

存储详情​

默认情况下,此实现使用 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;

事务管理​

JdbcIndexedSessionRepository 中的所有 JDBC 操作都是以事务方式执行的。为了防止由于与现有事务的干扰而导致意外行为(例如,在已经参与只读事务的线程中运行 save 操作),事务是以 REQUIRES_NEW 传播级别执行的。

使用 HazelcastIndexedSessionRepository​

HazelcastIndexedSessionRepository 是一个 SessionRepository 实现,它将会话存储在 Hazelcast 的分布式 IMap 中。在网络环境中,这通常与 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 才能使配置生效。你可以在 Samples and Guides (Start Here) 中找到完整的配置示例。

基本自定义​

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

  • maxInactiveIntervalInSeconds: 会话到期前的时间,以秒为单位。默认值为 1800 秒(30 分钟)

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

会话事件​

使用 MapListener 响应分布式 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;
}
  • 我们将 cookie 的名称自定义为 JSESSIONID。

  • 我们将 cookie 的路径自定义为 /(而不是默认的上下文根)。

  • 我们将域名模式(一个正则表达式)自定义为 ^.?\\.(\\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。