此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Session 3.3.1

此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Session 3.3.1

Session

A 是名称值对的简化。SessionMap

典型用法可能如以下列表所示:

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 我们创建一个具有泛型类型的实例,该实例扩展了 。泛型类型在我们的类中定义。SessionRepositorySSession
2 我们使用 our 创建一个 new 并将其分配给 类型的变量。SessionSessionRepositoryS
3 我们与 .在我们的示例中,我们演示了将 a 保存到 .SessionUserSession
4 现在,我们保存 .这就是为什么我们需要 泛型类型 .仅允许保存使用相同的 .这允许进行特定于实现的优化(即,仅编写已更改的属性)。SessionSSessionRepositorySessionSessionRepositorySessionRepository
5 我们从 .SessionSessionRepository
6 我们从我们那里获取持久化,而不需要显式转换我们的属性。UserSession

该 API 还提供与实例到期相关的属性。SessionSession

典型用法可能如以下列表所示:

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 我们创建一个具有泛型类型的实例,该实例扩展了 。泛型类型在我们的类中定义。SessionRepositorySSession
2 我们使用 our 创建一个 new 并将其分配给 类型的变量。SessionSessionRepositoryS
3 我们与 . 在我们的示例中,我们演示了在过期之前更新可以处于非活动状态的时间量。SessionSession
4 现在,我们保存 . 这就是为什么我们需要泛型类型 . 仅允许保存使用相同的 . 这允许进行特定于实现的优化(即,仅编写已更改的属性)。 保存时,上次访问时间会自动更新。SessionSSessionRepositorySessionSessionRepositorySessionRepositorySession
5 我们从 . 如果已过期,则结果将为 null。SessionSessionRepositorySession
1 我们创建一个具有泛型类型的实例,该实例扩展了 。泛型类型在我们的类中定义。SessionRepositorySSession
2 我们使用 our 创建一个 new 并将其分配给 类型的变量。SessionSessionRepositoryS
3 我们与 .在我们的示例中,我们演示了将 a 保存到 .SessionUserSession
4 现在,我们保存 .这就是为什么我们需要 泛型类型 .仅允许保存使用相同的 .这允许进行特定于实现的优化(即,仅编写已更改的属性)。SessionSSessionRepositorySessionSessionRepositorySessionRepository
5 我们从 .SessionSessionRepository
6 我们从我们那里获取持久化,而不需要显式转换我们的属性。UserSession
1 我们创建一个具有泛型类型的实例,该实例扩展了 。泛型类型在我们的类中定义。SessionRepositorySSession
2 我们使用 our 创建一个 new 并将其分配给 类型的变量。SessionSessionRepositoryS
3 我们与 . 在我们的示例中,我们演示了在过期之前更新可以处于非活动状态的时间量。SessionSession
4 现在,我们保存 . 这就是为什么我们需要泛型类型 . 仅允许保存使用相同的 . 这允许进行特定于实现的优化(即,仅编写已更改的属性)。 保存时,上次访问时间会自动更新。SessionSSessionRepositorySessionSessionRepositorySessionRepositorySession
5 我们从 . 如果已过期,则结果将为 null。SessionSessionRepositorySession

SessionRepository

A 负责创建、检索和持久化实例。SessionRepositorySession

如果可能,您不应直接与 a 或 . 相反,开发人员应该更喜欢与 HttpSession 和 WebSocket 集成进行交互,并通过 HttpSession 和 WebSocket 集成进行间接交互。SessionRepositorySessionSessionRepositorySession

FindByIndexNameSessionRepository

Spring Session 使用 a 的最基本 API 是 . 此 API 特意非常简单,以便您可以轻松地提供具有基本功能的其他实现。SessionSessionRepository

一些实现也可能选择实现。 例如,Spring 的 Redis、JDBC 和 Hazelcast 支持库都实现了 .SessionRepositoryFindByIndexNameSessionRepositoryFindByIndexNameSessionRepository

提供了一种方法来查找具有给定索引名称和索引值的所有会话。 作为所有提供的实现都支持的常见用例,您可以使用一种方便的方法来查找特定用户的所有会话。 这是通过确保使用用户名填充具有 的 的会话属性来完成的。 您有责任确保填充该属性,因为 Spring Session 不知道正在使用的身份验证机制。 可以在以下列表中看到如何使用它的示例:FindByIndexNameSessionRepositoryFindByIndexNameSessionRepositoryFindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME

String username = "username";
this.session.setAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username);
的某些实现提供钩子以自动索引其他会话属性。 例如,许多实现会自动确保使用索引名称 对当前 Spring Security 用户名进行索引。FindByIndexNameSessionRepositoryFindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME

为会话编制索引后,可以使用类似于以下内容的代码进行查找:

String username = "username";
Map<String, Session> sessionIdToSession = this.sessionRepository.findByPrincipalName(username);
的某些实现提供钩子以自动索引其他会话属性。 例如,许多实现会自动确保使用索引名称 对当前 Spring Security 用户名进行索引。FindByIndexNameSessionRepositoryFindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME

ReactiveSessionRepository

A 负责以非阻塞和响应方式创建、检索和持久化实例。ReactiveSessionRepositorySession

如果可能,您不应直接与 a 或 . 相反,您应该更喜欢与 WebSession 集成进行交互并间接通过 WebSession 集成进行交互。ReactiveSessionRepositorySessionReactiveSessionRepositorySession

@EnableSpringHttpSession

您可以将注释添加到类中,以将 公开为名为 的 Bean 。 要使用注释,必须提供单个 bean。 以下示例演示如何执行此操作:@EnableSpringHttpSession@ConfigurationSessionRepositoryFilterspringSessionRepositoryFilterSessionRepository

@EnableSpringHttpSession
@Configuration
public class SpringHttpSessionConfig {

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

}

请注意,没有为您配置会话过期的基础结构。 这是因为会话过期等操作高度依赖于实现。 这意味着,如果需要清理过期的会话,则负责清理过期的会话。

@EnableSpringWebSession

您可以将注释添加到类中,以将 公开为名为 的 Bean 。 要使用注释,必须提供单个 Bean。 以下示例演示如何执行此操作:@EnableSpringWebSession@ConfigurationWebSessionManagerwebSessionManagerReactiveSessionRepository

@Configuration(proxyBeanMethods = false)
@EnableSpringWebSession
public class SpringWebSessionConfig {

	@Bean
	public ReactiveSessionRepository reactiveSessionRepository() {
		return new ReactiveMapSessionRepository(new ConcurrentHashMap<>());
	}

}

请注意,没有为您配置会话过期的基础结构。 这是因为会话过期等操作高度依赖于实现。 这意味着,如果需要清理过期的会话,则负责清理过期的会话。

RedisSessionRepository

RedisSessionRepository是使用 Spring Data 的 . 在 Web 环境中,这通常与 结合使用。 请注意,此实现不支持发布会话事件。SessionRepositoryRedisOperationsSessionRepositoryFilter

实例化RedisSessionRepository

您可以在以下清单中看到如何创建新实例的典型示例:

RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

// ... configure redisTemplate ...

SessionRepository<? extends Session> repository = new RedisSessionRepository(redisTemplate);

有关如何创建 的其他信息,请参阅 Spring Data Redis 参考。RedisConnectionFactory

@EnableRedisHttpSession

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

enableIndexingAndEvents * enableIndexingAndEvents:是否使用 a 而不是 .默认值为 。 * maxInactiveIntervalInSeconds:会话过期前的时间量,以秒为单位。 * redisNamespace:允许为会话配置特定于应用程序的命名空间。Redis 密钥和通道 ID 以前缀 开头。 * flushMode:允许指定何时将数据写入 Redis。缺省值仅在 上调用时。 尽快写入 Redis 的值。RedisIndexedSessionRepositoryRedisSessionRepositoryfalse<redisNamespace>:saveSessionRepositoryFlushMode.IMMEDIATE

习惯RedisSerializer

您可以通过创建名为 实现 的 Bean 来自定义序列化。springSessionDefaultRedisSerializerRedisSerializer<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"
1 此键的后缀是 Spring Session 的会话标识符。

RedisIndexedSessionRepository

RedisIndexedSessionRepository是使用 Spring Data 的 . 在 Web 环境中,这通常与 结合使用。 实现支持并通过 .SessionRepositoryRedisOperationsSessionRepositoryFilterSessionDestroyedEventSessionCreatedEventSessionMessageListener

实例化RedisIndexedSessionRepository

您可以在以下清单中看到如何创建新实例的典型示例:

RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

// ... configure redisTemplate ...

SessionRepository<? extends Session> repository = new RedisIndexedSessionRepository(redisTemplate);

有关如何创建 的其他信息,请参阅 Spring Data Redis 参考。RedisConnectionFactory

@EnableRedisHttpSession(enableIndexingAndEvents = true)

在 Web 环境中,创建新环境的最简单方法是使用 . 您可以在示例和指南(从此处开始)中找到完整的示例用法。 您可以使用以下属性来自定义配置:RedisIndexedSessionRepository@EnableRedisHttpSession(enableIndexingAndEvents = true)

  • enableIndexingAndEvents:是否使用 a 而不是 .默认值为 。RedisIndexedSessionRepositoryRedisSessionRepositoryfalse

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

  • redisNamespace:允许为会话配置特定于应用程序的命名空间。Redis 密钥和通道 ID 以前缀 开头。<redisNamespace>:

  • flushMode:允许指定何时将数据写入 Redis。缺省值仅在 上调用时。 尽快写入 Redis 的值。saveSessionRepositoryFlushMode.IMMEDIATE

习惯RedisSerializer

您可以通过创建名为 实现 的 Bean 来自定义序列化。springSessionDefaultRedisSerializerRedisSerializer<Object>

Redis(瑞迪斯酒店)TaskExecutor

RedisIndexedSessionRepository订阅以使用 . 您可以通过创建名为 、 和 或 的 Bean 来自定义这些事件的调度方式。 您可以在此处找到有关配置 Redis 任务执行程序的更多详细信息。RedisMessageListenerContainerspringSessionRedisTaskExecutorspringSessionRedisSubscriptionExecutor

存储详细信息

以下部分概述了如何为每个操作更新 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

后续部分将介绍详细信息。

保存会话

每个会话都以 . 使用命令设置和更新每个会话。 以下示例显示了每个会话的存储方式:HashHMSET

HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe creationTime 1404360000000 \
	maxInactiveInterval 1800 \
	lastAccessedTime 1404360000000 \
	sessionAttr:attrName someAttrValue \
	sessionAttr:attrName2 someAttrValue2

在前面的示例中,以下关于会话的语句为 true:

  • 会话 ID 为 33fdd1b6-b496-4b33-9f7d-df96679d32fe。

  • 会话是在 1404360000000(自 1970 年 1 月 1 日格林威治标准时间午夜起以毫秒为单位)创建的。

  • 会话将在 1800 秒(30 分钟)后过期。

  • 该会话最后一次访问时间为 1404360000000(自 1970 年 1 月 1 日格林威治标准时间午夜以来以毫秒为单位)。

  • 会话有两个属性。 第一个是 ,其值为 。 第二个会话属性名为 ,其值为 。attrNamesomeAttrValueattrName2someAttrValue2

优化的写入

管理的实例会跟踪已更改的属性,并仅更新这些属性。 这意味着,如果一个属性被写入一次并读取多次,我们只需要写入该属性一次。 例如,假设上一节中 lsiting 中的 session 属性已更新。 保存时将运行以下命令:SessionRedisIndexedSessionRepositoryattrName2

HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe sessionAttr:attrName2 newValue

会话过期

过期是使用命令与每个会话关联的,该命令基于 . 以下示例显示了一个典型命令:EXPIRESession.getMaxInactiveInterval()EXPIRE

EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100

请注意,设置为会话实际过期后五分钟的过期时间。 这是必需的,以便在会话到期时可以访问会话的值。 会话本身在实际过期后五分钟设置过期,以确保它被清理,但只有在我们执行任何必要的处理之后。

该方法可确保不返回任何过期的会话。 这意味着在使用会话之前,您无需检查过期时间。SessionRepository.findById(String)

Spring Session 依赖于 Redis 的删除和过期密钥空间通知来分别触发 SessionDeletedEventSessionExpiredEvent。 或确保清理与 关联的资源。 例如,当您使用 Spring Session 的 WebSocket 支持时,Redis expired or delete 事件会触发与会话关联的任何 WebSocket 连接关闭。SessionDeletedEventSessionExpiredEventSession

不会直接在会话密钥本身上跟踪过期,因为这意味着会话数据将不再可用。相反,使用特殊会话过期密钥。在前面的示例中,expires 键如下所示:

APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe ""
EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800

当会话过期时,密钥被删除或过期,密钥空间通知将触发对实际会话的查找,并触发 a。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

SessionDeletedEvent并且都是 .SessionExpiredEventSessionDestroyedEvent

RedisIndexedSessionRepository支持在删除 A 时触发 A 或在 A 过期时触发 A。 这是确保正确清理与 关联的资源所必需的。SessionDeletedEventSessionSessionExpiredEventSessionSession

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

触发 或可通过 提供,该 侦听 Redis Keyspace 事件。 为此,需要启用通用命令和过期事件的 Redis Keyspace 事件。 以下示例演示如何执行此操作:SessionDeletedEventSessionExpiredEventSessionMessageListener

redis-cli config set notify-keyspace-events Egx

如果使用 ,则会自动管理和启用必要的 Redis 密钥空间事件。 但是,在安全的 Redis 环境中,config 命令处于禁用状态。 这意味着 Spring Session 无法为您配置 Redis Keyspace 事件。 要禁用自动配置,请添加为 bean。@EnableRedisHttpSession(enableIndexingAndEvents = true)SessionMessageListenerConfigureRedisAction.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

创建会话时,将向通道 ID 为 的 Redis 发送一个事件。 其中是会话 ID。事件的正文是创建的会话。spring:session:channel:created:33fdd1b6-b496-4b33-9f7d-df96679d32fe33fdd1b6-b496-4b33-9f7d-df96679d32fe

如果注册为 (默认值),则将 Redis 消息转换为 .MessageListenerRedisIndexedSessionRepositorySessionCreatedEvent

在 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 此密钥包含当时应删除的所有会话 ID。1418772300000

您还可以查看每个会话的属性。 以下示例演示如何执行此操作:

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"
该方法可确保不返回任何过期的会话。 这意味着在使用会话之前,您无需检查过期时间。SessionRepository.findById(String)
我们不会显式删除密钥,因为在某些情况下,可能存在争用条件,该条件错误地将密钥标识为已过期,而密钥并非已过期。 如果不使用分布式锁(这会降低我们的性能),就无法确保过期映射的一致性。 通过简单地访问密钥,我们确保仅在该密钥上的 TTL 过期时才删除该密钥。
1 此键的后缀是 Spring Session 的会话标识符。
2 此密钥包含当时应删除的所有会话 ID。1418772300000

ReactiveRedisSessionRepository

ReactiveRedisSessionRepository是使用 Spring Data 的 . 在 Web 环境中,这通常与 结合使用。ReactiveSessionRepositoryReactiveRedisOperationsWebSessionStore

实例化ReactiveRedisSessionRepository

以下示例演示如何创建新实例:

// ... create and configure connectionFactory and serializationContext ...

ReactiveRedisTemplate<String, Object> redisTemplate = new ReactiveRedisTemplate<>(connectionFactory,
		serializationContext);

ReactiveSessionRepository<? extends Session> repository = new ReactiveRedisSessionRepository(redisTemplate);

有关如何创建 的其他信息,请参阅 Spring Data Redis 参考。ReactiveRedisConnectionFactory

@EnableRedisWebSession

在 Web 环境中,创建新环境的最简单方法是使用 . 您可以使用以下属性来自定义配置:ReactiveRedisSessionRepository@EnableRedisWebSession

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

  • redisNamespace:允许为会话配置特定于应用程序的命名空间。Redis 密钥和通道 ID 以前缀 的 q 开头。<redisNamespace>:

  • flushMode:允许指定何时将数据写入 Redis。缺省值仅在 上调用时。 尽快写入 Redis 的值。saveReactiveSessionRepositoryFlushMode.IMMEDIATE

优化的写入

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

在 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"
1 此键的后缀是 Spring Session 的会话标识符。

MapSessionRepository

允许保留在 中,键是 ID,值是 。 您可以将带有 a 的实现用作测试或便利机制。 或者,您可以将其与分布式实现一起使用。例如,它可以与 Hazelcast 一起使用。MapSessionRepositorySessionMapSessionSessionConcurrentHashMapMap

实例化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 实现。MapListenerSessionCreatedEventSessionDeletedEventSessionExpiredEvent

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

	./gradlew :samples:hazelcast-spring:tomcatRun

ReactiveMapSessionRepository

允许保留在 中,键是 ID,值是 。 您可以将带有 a 的实现用作测试或便利机制。 或者,您可以将其与分布式实现一起使用,但要求提供的必须是非阻塞的。ReactiveMapSessionRepositorySessionMapSessionSessionConcurrentHashMapMapMap

JdbcIndexedSessionRepository

JdbcIndexedSessionRepository是一种使用 Spring 将会话存储在关系数据库中的实现。 在 Web 环境中,这通常与 结合使用。 请注意,此实现不支持发布会话事件。SessionRepositoryJdbcOperationsSessionRepositoryFilter

实例化JdbcIndexedSessionRepository

以下示例演示如何创建新实例:

JdbcTemplate jdbcTemplate = new JdbcTemplate();

// ... configure jdbcTemplate ...

TransactionTemplate transactionTemplate = new TransactionTemplate();

// ... configure transactionTemplate ...

SessionRepository<? extends Session> repository = new JdbcIndexedSessionRepository(jdbcTemplate,
		transactionTemplate);

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

@EnableJdbcHttpSession

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

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

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

定制LobHandler

您可以通过创建名为 实现 的 Bean 来自定义 BLOB 处理。springSessionLobHandlerLobHandler

定制ConversionService

您可以通过提供实例来自定义会话的默认序列化和反序列化。 在典型的 Spring 环境中工作时,会自动拾取默认 bean(命名 )并用于序列化和反序列化。 但是,您可以通过提供名为 的 Bean 来覆盖缺省值。ConversionServiceConversionServiceconversionServiceConversionServicespringSessionConversionService

存储详细信息

默认情况下,此实现使用 和 表来存储会话。 请注意,您可以自定义表名,如前所述。在这种情况下,用于存储属性的表使用提供的表名来命名,该表的后缀为 。 如果需要进一步的自定义,可以使用 setter 方法自定义存储库使用的 SQL 查询。在这种情况下,您需要手动配置 Bean。SPRING_SESSIONSPRING_SESSION_ATTRIBUTES_ATTRIBUTESset*QuerysessionRepository

由于各种数据库供应商之间的差异,尤其是在存储二进制数据时,请确保使用特定于数据库的 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;

交易管理

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

HazelcastIndexedSessionRepository

HazelcastIndexedSessionRepository是一种将会话存储在 Hazelcast 的分布式 . 在 Web 环境中,这通常与 结合使用。SessionRepositoryIMapSessionRepositoryFilter

实例化HazelcastIndexedSessionRepository

以下示例演示如何创建新实例:

Config config = new Config();

// ... configure Hazelcast ...

HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(config);

HazelcastIndexedSessionRepository repository = new HazelcastIndexedSessionRepository(hazelcastInstance);

有关如何创建和配置 Hazelcast 实例的其他信息,请参阅 Hazelcast 文档

@EnableHazelcastHttpSession

要使用 Hazelcast 作为 的支持源,您可以将注释添加到类中。 这样做扩展了注释提供的功能,但在 Hazelcast 中为您服务。 您必须提供单个 Bean 才能使配置正常工作。 您可以在示例和指南(从此处开始)中找到完整的配置示例。SessionRepository@EnableHazelcastHttpSession@Configuration@EnableSpringHttpSessionSessionRepositoryHazelcastInstance

基本定制

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

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

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

会议活动

使用 a 响应从分布式中添加、逐出和删除的条目会导致这些事件触发 、 和 事件(分别)通过 的发布。MapListenerMapSessionCreatedEventSessionExpiredEventSessionDeletedEventApplicationEventPublisher

存储详细信息

会话存储在 Hazelcast 的分布式中。 接口方法用于 和 会话。 此外,该方法还支持操作以及适当的操作(需要向 Hazelcast 注册)。有关此配置的更多详细信息,请参阅 Hazelcast Spring 示例。 会话的过期由 Hazelcast 的支持处理,该支持在条目进入 .闲置时间超过生存时间的条目(会话)将自动从 .IMapIMapget()put()values()FindByIndexNameSessionRepository#findByIndexNameAndIndexValueValueExtractorIMapput()IMapIMap

您不需要配置任何设置,例如 Hazelcast 配置中的设置。max-idle-secondstime-to-live-secondsIMap

请注意,如果您使用 Hazelcast 来持久化您的会话,则在从以下位置重新加载会话时,以下限制适用:MapStoreIMapMapStore

  • 重新加载触发器会导致重新发布EntryAddedListenerSessionCreatedEvent

  • 重新加载使用默认 TTL 来表示会话丢失其原始 TTL 的给定结果IMap

CookieSerializer

A 负责定义会话 cookie 的编写方式。 Spring Session 附带了一个默认实现,使用 .CookieSerializerDefaultCookieSerializer

以 Bean 形式公开CookieSerializer

当您使用类似 的配置时,将 公开为 Spring bean 会扩充现有配置。CookieSerializer@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 我们将域名模式(正则表达式)自定义为 。 这允许跨域和应用程序共享会话。 如果正则表达式不匹配,则不设置属性域,并使用现有属性域。 如果正则表达式匹配,则第一个分组用作域。 这意味着对 child.example.com 的请求将域设置为 。 但是,对 localhost:8080/192.168.1.100:8080/ 的请求会使 cookie 保持未设置状态,因此,它仍然在开发中工作,无需对生产进行任何更改。^.?\\.(\\w\\.[a-z]+)$example.com
您应仅匹配有效的域字符,因为该域名会反映在响应中。 这样做可以防止恶意用户执行 HTTP 响应拆分等攻击。

定制CookieSerializer

您可以通过在 .DefaultCookieSerializer

  • cookieName:要使用的 Cookie 的名称。 违约:。SESSION

  • useSecureCookie:指定是否应使用安全 Cookie。 默认值:使用创建时的值。HttpServletRequest.isSecure()

  • cookiePath:cookie 的路径。 默认值:上下文根。

  • cookieMaxAge:指定在创建会话时要设置的 cookie 的最长期限。 默认值:,表示在浏览器关闭时应删除 cookie。-1

  • jvmRoute:指定要附加到会话 ID 并包含在 Cookie 中的后缀。 用于确定要路由到哪个 JVM 以实现会话相关性。 对于某些实现(即 Redis),此选项不会提供任何性能优势。 但是,它可以帮助跟踪特定用户的日志。

  • domainName:允许指定用于 cookie 的特定域名。 此选项易于理解,但通常需要在开发环境和生产环境之间进行不同的配置。 请看作另一种选择。domainNamePattern

  • domainNamePattern:一种不区分大小写的模式,用于从 . 该模式应提供用于提取 Cookie 域值的单个分组。 如果正则表达式不匹配,则不设置属性域,并使用现有属性域。 如果正则表达式匹配,则第一个分组用作域。HttpServletRequest#getServerName()

  • sameSite:cookie 指令的值。 若要禁用 cookie 指令的序列化,可以将此值设置为 。 违约:SameSiteSameSitenullLax

您应仅匹配有效的域字符,因为该域名会反映在响应中。 这样做可以防止恶意用户执行 HTTP 响应拆分等攻击。
1 我们将 cookie 的名称自定义为 。JSESSIONID
2 我们将 cookie 的路径自定义为(而不是上下文根的默认路径)。/
3 我们将域名模式(正则表达式)自定义为 。 这允许跨域和应用程序共享会话。 如果正则表达式不匹配,则不设置属性域,并使用现有属性域。 如果正则表达式匹配,则第一个分组用作域。 这意味着对 child.example.com 的请求将域设置为 。 但是,对 localhost:8080/192.168.1.100:8080/ 的请求会使 cookie 保持未设置状态,因此,它仍然在开发中工作,无需对生产进行任何更改。^.?\\.(\\w\\.[a-z]+)$example.com
您应仅匹配有效的域字符,因为该域名会反映在响应中。 这样做可以防止恶意用户执行 HTTP 响应拆分等攻击。
您应仅匹配有效的域字符,因为该域名会反映在响应中。 这样做可以防止恶意用户执行 HTTP 响应拆分等攻击。

定制SessionRepository

实现自定义 SessionRepository API 应该是一项相当简单的任务。 将自定义实现与@EnableSpringHttpSession支持相结合,可以重用现有的 Spring Session 配置工具和基础结构。 然而,有几个方面值得仔细考虑。

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

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