使用@EnableSpringWebSession

使用@EnableSpringWebSession

Spring Session

API 文档

API 文档

您可以浏览完整的 Javadoc 在线文档。关键的 API 在以下章节中描述

使用 Session

使用 SessionRepository

使用 FindByIndexNameSessionRepository

使用 ReactiveSessionRepository

使用 @EnableSpringHttpSession

使用 @EnableSpringWebSession

使用 RedisIndexedSessionRepository

使用 ReactiveRedisSessionRepository

使用 MapSessionRepository

使用 ReactiveMapSessionRepository

使用 JdbcIndexedSessionRepository

使用 HazelcastIndexedSessionRepository

使用 CookieSerializer

使用 Session

一个 Session 是一个简化的键值对 Map。

典型的用法可能如下所示

class RepositoryDemo {

private SessionRepository 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,扩展自 Session 的 SessionRepository 实例。泛型类型在我们类中定义。

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 {

private SessionRepository 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,扩展自 Session 的 SessionRepository 实例。泛型类型在我们类中定义。

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 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 是一个 SessionRepository,它使用 Spring Data 的 RedisOperations 实现。在 web 环境中,这通常与 SessionRepositoryFilter 结合使用。请注意,此实现不支持发布会话事件。

实例化一个 RedisSessionRepository

以下清单展示了如何创建一个新实例的典型示例

RedisTemplate redisTemplate = new RedisTemplate<>();

// ... configure redisTemplate ...

SessionRepository repository = new RedisSessionRepository(redisTemplate);

有关如何创建 RedisConnectionFactory 的更多信息,请参阅 Spring Data Redis 参考文档。

使用 @EnableRedisHttpSession

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

enableIndexingAndEvents * enableIndexingAndEvents: 是否使用 RedisIndexedSessionRepository 而不是 RedisSessionRepository。默认值为 false。 * maxInactiveIntervalInSeconds: 会话过期前的时间量,以秒为单位。 * redisNamespace: 允许为会话配置特定于应用的命名空间。Redis 键和通道 ID 以 : 为前缀开始。 * flushMode: 允许指定何时将数据写入 Redis。默认值是仅当在 SessionRepository 上调用 save 时。FlushMode.IMMEDIATE 值表示尽快将数据写入 Redis。

自定义 RedisSerializer

您可以通过创建一个名为 springSessionDefaultRedisSerializer 并实现了 RedisSerializer 的 bean 来自定义序列化。

在 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 是一个 SessionRepository,它使用 Spring Data 的 RedisOperations 实现。在 web 环境中,这通常与 SessionRepositoryFilter 结合使用。此实现通过 SessionMessageListener 支持 SessionDestroyedEvent 和 SessionCreatedEvent。

实例化一个 RedisIndexedSessionRepository

以下清单展示了如何创建一个新实例的典型示例

RedisTemplate redisTemplate = new RedisTemplate<>();

// ... configure redisTemplate ...

SessionRepository repository = new RedisIndexedSessionRepository(redisTemplate);

有关如何创建 RedisConnectionFactory 的更多信息,请参阅 Spring Data Redis 参考文档。

使用 @EnableRedisHttpSession(enableIndexingAndEvents = true)

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

enableIndexingAndEvents: 是否使用 RedisIndexedSessionRepository 而不是 RedisSessionRepository。默认值为 false。

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

redisNamespace: 允许为会话配置特定于应用的命名空间。Redis 键和通道 ID 以 : 为前缀开始。

flushMode: 允许指定何时将数据写入 Redis。默认值是仅当在 SessionRepository 上调用 save 时。FlushMode.IMMEDIATE 值表示尽快将数据写入 Redis。

自定义 RedisSerializer

您可以通过创建一个名为 springSessionDefaultRedisSerializer 并实现了 RedisSerializer 的 bean 来自定义序列化。

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(自 GMT 1970 年 1 月 1 日午夜以来的毫秒数)。

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

会话最后访问于 1404360000000(自 GMT 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 连接关闭。

过期时间并非直接在 session key 本身上跟踪,因为这意味着会话数据将不再可用。相反,使用了一个特殊的 session expires key。在前面的示例中, expires key 如下所示

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

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

当一个 session expires key 被删除或过期时,键空间通知会触发对实际会话的查找,并触发一个 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 连接。

触发 SessionDeletedEvent 或 SessionExpiredEvent 通过 SessionMessageListener 实现,它监听 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 配置,您可以使用以下代码

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 redisTemplate = new ReactiveRedisTemplate<>(connectionFactory,

serializationContext);

ReactiveSessionRepository repository = new ReactiveRedisSessionRepository(redisTemplate);

有关如何创建 ReactiveRedisConnectionFactory 的更多信息,请参阅 Spring Data Redis 参考文档。

使用 @EnableRedisWebSession

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

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

redisNamespace: 允许为会话配置特定于应用的命名空间。Redis 键和通道 ID 以 : 为前缀开始。

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 允许将 Session 持久化到 Map 中,其中键是 Session ID,值是 Session。您可以将此实现与 ConcurrentHashMap 一起用作测试或便捷机制。或者,您可以将其与分布式 Map 实现一起使用。例如,它可以与 Hazelcast 一起使用。

实例化 MapSessionRepository

以下示例展示了如何创建一个新实例

SessionRepository 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 repository = new JdbcIndexedSessionRepository(jdbcTemplate,

transactionTemplate);

有关如何创建和配置 JdbcTemplate 和 PlatformTransactionManager 的更多信息,请参阅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_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;

事务管理

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 中条目被添加、逐出和移除事件,会导致这些事件(分别)通过 ApplicationEventPublisher 触发发布 SessionCreatedEvent、SessionExpiredEvent 和 SessionDeletedEvent 事件。

存储详情

会话存储在 Hazelcast 的分布式 IMap 中。IMap 接口方法用于 get() 和 put() 会话。此外,values() 方法支持一个 FindByIndexNameSessionRepository#findByIndexNameAndIndexValue 操作,并配合适当的 ValueExtractor(需要向 Hazelcast 注册)。有关此配置的更多详情,请参阅 Hazelcast Spring Sample。IMap 中会话的过期由 Hazelcast 支持在条目被 put() 到 IMap 时设置生存时间(TTL)来处理。闲置时间超过生存时间的条目(会话)会自动从 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 Response Splitting 等攻击。

定制 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 Response Splitting 等攻击。

定制 SessionRepository

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

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

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

Spring Security 集成 升级

🌈 相关推荐

普通弹子锁的几种开启方法
博大365

普通弹子锁的几种开启方法

📅 07-20 👁️ 5328
宾利欧陆 GT3
office365 登录

宾利欧陆 GT3

📅 07-07 👁️ 5853
苹果AirPods报价
博大365

苹果AirPods报价

📅 06-29 👁️ 3510

🎨 用创意点亮世界 ✨