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 我们创建一个带有泛型类型 SSessionRepository 实例,S 扩展了 Session。泛型类型在我们的类中定义。
2 我们使用 SessionRepository 创建一个新的 Session 并将其赋值给 S 类型的变量。
3 我们与 Session 交互。在我们的示例中,我们演示了将 User 保存到 Session
4 我们现在保存 Session。这就是为什么我们需要泛型类型 SSessionRepository 只允许保存使用相同 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 我们创建一个带有泛型类型 SSessionRepository 实例,S 扩展了 Session。泛型类型在我们的类中定义。
2 我们使用 SessionRepository 创建一个新的 Session 并将其赋值给 S 类型的变量。
3 我们与 Session 交互。在我们的示例中,我们演示了更新 Session 在过期前可以不活动的持续时间。
4 我们现在保存 Session。这就是为什么我们需要泛型类型 SSessionRepository 只允许保存使用相同 SessionRepository 创建或检索的 Session 实例。这允许 SessionRepository 进行特定于实现的优化(即只写入已更改的属性)。当 Session 保存时,上次访问时间会自动更新。
5 我们从 SessionRepository 中检索 Session。如果 Session 已过期,结果将为 null。

使用 SessionRepository

SessionRepository 负责创建、检索和持久化 Session 实例。

如果可能,您不应直接与 SessionRepositorySession 交互。相反,开发人员应优先通过 HttpSessionWebSocket 集成间接与 SessionRepositorySession 交互。

使用 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 实例。

如果可能,您不应直接与 ReactiveSessionRepositorySession 交互。相反,您应优先通过 WebSession 集成间接与 ReactiveSessionRepositorySession 交互。

使用 @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。

自定义 RedisSerializer

您可以通过创建名为 springSessionDefaultRedisSerializer 的 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)
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 支持 SessionDestroyedEventSessionCreatedEvent 的发布。

实例化 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(实现 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 的删除和过期 键空间通知 来分别触发 SessionDeletedEventSessionExpiredEventSessionDeletedEventSessionExpiredEvent 确保与 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 过期时才删除该键。

SessionDeletedEventSessionExpiredEvent

SessionDeletedEventSessionExpiredEvent 都是 SessionDestroyedEvent 的类型。

RedisIndexedSessionRepository 支持在 Session 被删除时触发 SessionDeletedEvent,或者在 Session 过期时触发 SessionExpiredEvent。这对于确保与 Session 关联的资源得到正确清理是必要的。

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

通过 SessionMessageListener 提供 SessionDeletedEventSessionExpiredEvent 的触发,该监听器监听 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。

优化写入

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)
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 实现,支持触发 SessionCreatedEventSessionDeletedEventSessionExpiredEvent

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

	./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);

有关如何创建和配置 JdbcTemplatePlatformTransactionManager 的更多信息,请参阅 Spring Framework 参考文档

使用 @EnableJdbcHttpSession

在 Web 环境中,创建新的 JdbcIndexedSessionRepository 的最简单方法是使用 @EnableJdbcHttpSession。您可以在 示例和指南(从这里开始) 中找到完整的示例用法。您可以使用以下属性自定义配置。

  • tableName:Spring Session 用于存储会话的数据库表的名称。

  • maxInactiveIntervalInSeconds:会话过期前的时间量,以秒为单位。

自定义 LobHandler

您可以通过创建名为 springSessionLobHandler 并实现 LobHandler 的 bean 来定制 BLOB 处理。

自定义 ConversionService

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

存储详情

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

事务管理

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

使用 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 触发 SessionCreatedEventSessionExpiredEventSessionDeletedEvent 事件(分别)。

存储详情

会话存储在 Hazelcast 的分布式 IMap 中。IMap 接口方法用于 get()put() 会话。此外,values() 方法支持 FindByIndexNameSessionRepository#findByIndexNameAndIndexValue 操作,以及适当的 ValueExtractor(需要注册到 Hazelcast)。有关此配置的更多详细信息,请参阅 Hazelcast Spring 示例IMap 中会话的过期由 Hazelcast 对条目在 put()IMap 时设置生存时间的支持来处理。闲置时间超过生存时间的条目(会话)将自动从 IMap 中删除。

您不需要在 Hazelcast 配置中为 IMap 配置任何设置,例如 max-idle-secondstime-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 域值的分组。如果正则表达式不匹配,则不设置任何域并使用现有域。如果正则表达式匹配,则第一个分组用作域。

  • sameSiteSameSite Cookie 指令的值。要禁用 SameSite Cookie 指令的序列化,您可以将此值设置为 null。默认值:Lax

您应该只匹配有效的域名字符,因为域名会反映在响应中。这样做可以防止恶意用户执行诸如 HTTP 响应拆分之类的攻击。

自定义 SessionRepository

实现自定义 SessionRepository API 应该是一项相当直接的任务。将自定义实现与 @EnableSpringHttpSession 支持相结合,可以重用现有的 Spring Session 配置设施和基础设施。然而,有几个方面值得更密切的考虑。

在 HTTP 请求的生命周期中,HttpSession 通常会被持久化到 SessionRepository 两次。第一次持久化操作是为了确保客户端在访问会话 ID 后立即可以使用会话,并且在会话提交后也需要写入,因为可能会对会话进行进一步的修改。考虑到这一点,我们通常建议 SessionRepository 实现跟踪更改,以确保只保存增量。这在高度并发的环境中尤为重要,其中多个请求操作相同的 HttpSession,因此会导致竞争条件,请求会覆盖彼此对会话属性的更改。Spring Session 提供的所有 SessionRepository 实现都使用上述方法持久化会话更改,并可在您实现自定义 SessionRepository 时用作指导。

请注意,对于实现自定义 ReactiveSessionRepository,也适用相同的建议。在这种情况下,您应该使用 @EnableSpringWebSession

© . This site is unofficial and not affiliated with VMware.