|
对于最新稳定版本,请使用 Spring Session 4.0.2! |
API 文档
您可以在线浏览完整的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 ...
}
| 1 | 我们创建一个SessionRepository实例,并使用扩展了Session的泛型类型S。泛型类型在我们的类中定义。 |
| 2 | 我们通过使用我们的SessionRepository来创建一个新的Session,并将其赋值给一个类型为S的变量。 |
| 3 | 我们与Session进行交互。在我们的示例中,我们展示了将一个User保存到Session的过程。 |
| 4 | 我们现在保存了Session。这就是我们需要泛型类型S的原因。SessionRepository只允许保存由使用相同的SessionRepository创建或检索的Session实例。这使得SessionRepository能够进行实现特定的优化(也就是说,只写已经改变的属性)。 |
| 5 | 我们从SessionRepository中检索到了Session。 |
| 6 | 我们从Session中获得了持久化的User,而无需显式地将属性进行类型转换。 |
The 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 | 我们创建一个SessionRepository实例,并使用扩展了Session的泛型类型S。泛型类型在我们的类中定义。 |
| 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 的最基础 API 用于使用一个 Session 是 SessionRepository。
此 API 意图非常简单,以便您可以轻松提供具有基本功能的其他实现。
有些SessionRepository实现也可能选择实现FindByIndexNameSessionRepository。
例如,Spring的Redis、JDBC和Hazelcast支持库都实现了FindByIndexNameSessionRepository。
FindByIndexNameSessionRepository 提供了一个方法,用于查找具有给定索引名称和索引值的所有会话。
作为一种由所有提供的 FindByIndexNameSessionRepository 实现支持的常见用例,您可以使用一个方便的方法来查找特定用户的所有会话。
这可以通过确保带有名称为 FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME 的会话属性被填充用户名来实现。
由于 Spring Session 并不了解正在使用的身份验证机制,因此您有责任确保该属性被正确填充。
以下是如何使用此功能的一个示例:
String username = "username";
this.session.setAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username);
一些实现FindByIndexNameSessionRepository提供了自动索引其他会话属性的钩子。
例如,许多实现会自动确保当前Spring Security用户名使用索引名FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME进行索引。 |
一旦会话被索引,您可以使用类似于以下的代码进行查找:
String username = "username";
Map<String, Session> sessionIdToSession = this.sessionRepository.findByPrincipalName(username);
使用ReactiveSessionRepository
一个ReactiveSessionRepository负责以非阻塞和响应式的方式创建、检索和持久化Session实例。
如果可能,您不应该直接与ReactiveSessionRepository或Session进行交互。
相反,您应该通过WebSession集成间接地与ReactiveSessionRepository和Session进行交互。
使用@EnableSpringHttpSession
您可以将@EnableSpringHttpSession注解添加到@Configuration类中,以使SessionRepositoryFilter作为名为springSessionRepositoryFilter的bean暴露出来。
要使用该注解,您必须提供一个单一的SessionRepository bean。
以下示例展示了如何做到这一点:
@EnableSpringHttpSession
@Configuration
public class SpringHttpSessionConfig {
@Bean
public MapSessionRepository sessionRepository() {
return new MapSessionRepository(new ConcurrentHashMap<>());
}
}
注意,Spring框架未为您配置会话过期的基础设施。 这是因为像会话过期这样的事情高度依赖于具体的实现。 这意味着,如果您需要清理已过期的会话,则必须负责清理这些已过期的会话。
使用@EnableSpringWebSession
您可以将@EnableSpringWebSession注解添加到@Configuration类中,以暴露WebSessionManager作为名为webSessionManager的bean。
要使用该注解,您必须提供一个单一的ReactiveSessionRepository bean。下面的例子展示了如何实现这一点:
@Configuration(proxyBeanMethods = false)
@EnableSpringWebSession
public class SpringWebSessionConfig {
@Bean
public ReactiveSessionRepository reactiveSessionRepository() {
return new ReactiveMapSessionRepository(new ConcurrentHashMap<>());
}
}
请注意,Spring框架并未为您配置会话过期的基础设施。 这是因为像会话过期这样的事情高度依赖于具体的实现方式。 这意味着,如果您需要清理过期的会话,请自行处理这些过期的会话。
使用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。
查看 Redis 中的会话
在安装了redis-cli之后,您可以通过使用 redis-cli来检查 Redis 中的值。 例如,您可以将以下命令输入到终端窗口中:
$ redis-cli
redis 127.0.0.1:6379> keys *
1) "spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021" (1)
| 1 | 此键的后缀是 Spring Session 的会话标识符。 |
您还可以通过使用hkeys命令来查看每个会话的属性。
以下是一个示例,展示了如何实现这一点:
redis 127.0.0.1:6379> hkeys spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021
1) "lastAccessedTime"
2) "creationTime"
3) "maxInactiveInterval"
4) "sessionAttr:username"
redis 127.0.0.1:6379> hget spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021 sessionAttr:username
"\xac\xed\x00\x05t\x00\x03rob"
使用RedisIndexedSessionRepository
RedisIndexedSessionRepository 是一个由 Spring Data 的 RedisOperations 实现的 SessionRepository。
在 web 环境中,这通常与 SessionRepositoryFilter 结合使用。
实现支持通过 SessionMessageListener 提供的 SessionDestroyedEvent 和 SessionCreatedEvent。
实例化一个RedisIndexedSessionRepository
您可以在以下示例中看到如何创建一个新实例的方法:
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// ... configure redisTemplate ...
SessionRepository<? extends Session> repository = new RedisIndexedSessionRepository(redisTemplate);
对于如何创建一个RedisConnectionFactory的额外信息,请参阅Spring Data Redis 参考文档。
使用@EnableRedisHttpSession(enableIndexingAndEvents = true)
在Web环境中,创建一个新的RedisIndexedSessionRepository的最简单方法是使用@EnableRedisHttpSession(enableIndexingAndEvents = true)。
您可以在示例和指南(从这里开始)中找到完整的用法示例。
您可以使用以下属性来自定义配置:
-
enableIndexingAndEvents: 是否使用
RedisIndexedSessionRepository而不是RedisSessionRepository。默认值是false。 -
maxInactiveIntervalInSeconds: 会话在过期前的空闲时间(以秒为单位)。
-
redisNamespace: 允许为会话配置应用程序特定的命名空间。Redis键和频道ID以前缀
<redisNamespace>:开始。 -
flushMode: 允许指定何时将数据写入Redis。默认情况下,只有在调用
save时才写入SessionRepository。 一个值为FlushMode.IMMEDIATE的设置会在可能的情况下尽快写入Redis。
RedisTaskExecutor
RedisIndexedSessionRepository 是订阅从 Redis 接收事件的。它使用的是一个 RedisMessageListenerContainer。
你可以通过创建名为 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
在前面的例子中,以下关于会话的陈述为真:
-
session ID是 33fdd1b6-b496-4b33-9f7d-df96679d32fe。
-
会话创建于1404360000000(自1970年1月1日格林尼治时间午夜以来的毫秒数)。
-
The session expires in 1800 seconds (30 minutes).
-
The session was last accessed at 1404360000000 (in milliseconds since midnight of 1/1/1970 GMT).
-
该会话有两个属性。 第一个是
attrName,其值为someAttrValue。 第二个会话属性名为attrName2,其值为someAttrValue2。
优化的写入
The Session 实例由 RedisIndexedSessionRepository 管理,会跟踪已更改的属性并仅更新这些属性。
这意味着,如果一个属性只写入一次而被读取多次,则我们只需写入该属性一次。
例如,假设前一节列表中的 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
注意,设置的过期时间是在会话实际过期后五分钟。这是必要的,以便在会话过期时仍能访问会话值。 为此,在会话实际过期后的五分钟内设置了过期时间以确保其被清理,但在此之前我们还需要执行任何必要的处理。
The SessionRepository.findById(String) method ensures that no expired sessions are returned.
This means that you need not check the expiration before using a session. |
Spring Session 依赖 Redis 的删除和过期 键空间通知,分别触发 SessionDeletedEvent 和 SessionExpiredEvent。
SessionDeletedEvent 或 SessionExpiredEvent 确保与 Session 关联的资源被清理。
例如,当您使用 Spring Session 的 WebSocket 支持时,Redis 的过期或删除事件会触发与该会话关联的任何 WebSocket 连接被关闭。
会话密钥本身不直接跟踪过期时间,因为这意味着会话数据将不再可用。相反,使用一个特殊的会话过期键。在前面的示例中,会话过期键如下所示:
APPEND spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe "" EXPIRE spring:session:sessions:expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe 1800
当会话过期或键被删除或过期时,键空间通知会触发实际会话的查找,并发送一个SessionDestroyedEvent。
依赖于Redis过期时间独占地一个问题是,如果键没有被访问,Redis不会保证在何时触发过期事件。
具体来说,Redis用于清理过期键的后台任务是一个低优先级的任务,并且可能不会触发键的过期。
有关更多详细信息,请参阅Redis文档中的过期事件时间部分。
要规避已过期事件不可保证发生的情况,我们可以在预计过期时访问每个键。 这意味着,如果键的TTL已经过期,在我们尝试访问该键时,Redis会移除该键并触发过期事件。
由于这个原因,每次会话过期也会追踪到最近的分钟。 这使得后台任务可以访问可能已过期的会话,以确保在更确定的方式下触发 Redis 过期事件。 以下示例展示了这些事件:
SADD spring:session:expirations:1439245080000 expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe EXPIRE spring:session:expirations1439245080000 2100
然后后台任务会使用这些映射来显式地请求每个键。 通过访问键而不是删除它,我们可以确保只有在超时(TTL)过期时 Redis 才会自动删除该键。
| 我们不显式地删除键,因为在某些情况下,可能会出现竞态条件,错误地将一个未过期的键识别为已过期。 除非使用分布式锁(这会严重影响我们的性能),否则无法保证过期映射的一致性。 通过简单地访问该键,我们可以确保只有在该键的 TTL 过期时才会删除该键。 |
SessionDeletedEvent和SessionExpiredEvent
SessionDeletedEvent 和 SessionExpiredEvent 都是 SessionDestroyedEvent 的类型。
RedisIndexedSessionRepository 支持在删除一个 Session 时触发一个 SessionDeletedEvent,或者在 Session 过期时触发一个 SessionExpiredEvent。
这确保了与 Session 关联的资源能够正确清理。
例如,在与WebSocket集成时,SessionDestroyedEvent负责关闭任何正在活动的WebSocket连接。
firing SessionDeletedEvent 或 SessionExpiredEvent 是通过 SessionMessageListener 实现的,SessionMessageListener 监听 Redis 键空间事件。
为了使这一功能正常工作,需要启用通用命令和过期事件的 Redis 键空间事件。以下示例展示了如何实现这一点:
redis-cli config set notify-keyspace-events Egx
如果使用@EnableRedisHttpSession(enableIndexingAndEvents = true),Spring会自动管理SessionMessageListener并启用必要的Redis键空间事件。
但在安全的Redis环境中,配置命令被禁用。
这意味着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 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
以下示例展示了如何创建一个新的实例:<br>
// ... create and configure connectionFactory and serializationContext ...
ReactiveRedisTemplate<String, Object> redisTemplate = new ReactiveRedisTemplate<>(connectionFactory,
serializationContext);
ReactiveSessionRepository<? extends Session> repository = new ReactiveRedisSessionRepository(redisTemplate);
对于如何创建一个ReactiveRedisConnectionFactory的额外信息,请参阅Spring Data Redis 参考文档。
使用@EnableRedisWebSession
在Web环境中,创建一个新的ReactiveRedisSessionRepository最简单的方法是使用@EnableRedisWebSession。
你可以使用以下属性来自定义配置:
-
maxInactiveIntervalInSeconds: 每次请求之间会话在秒数内失效的时间
-
redisNamespace: 允许为会话配置应用程序特定的命名空间。Redis键和频道ID以
<redisNamespace>:前缀q开头。 -
flushMode: 允许指定何时将数据写入Redis。默认情况下,只有在调用
save时才写入ReactiveSessionRepository。 一个值为FlushMode.IMMEDIATE的设置会在可能的情况下尽快写入Redis。
查看 Redis 中的会话
在安装了redis-cli之后,您可以通过使用 redis-cli来检查 Redis 中的值。 例如,您可以将以下命令输入到终端窗口中:
$ redis-cli
redis 127.0.0.1:6379> keys *
1) "spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021" (1)
| 1 | 此键的后缀是 Spring Session 的会话标识符。 |
您还可以通过使用hkeys命令来查看每个会话的属性。
以下是一个示例,展示了如何实现这一点:
redis 127.0.0.1:6379> hkeys spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021
1) "lastAccessedTime"
2) "creationTime"
3) "maxInactiveInterval"
4) "sessionAttr:username"
redis 127.0.0.1:6379> hget spring:session:sessions:4fc39ce3-63b3-4e17-b1c4-5e1ed96fb021 sessionAttr:username
"\xac\xed\x00\x05t\x00\x03rob"
使用MapSessionRepository
The MapSessionRepository 允许将 Session 持久化到 Map 中,其中键为 Session ID,值为 Session。
您可以使用此实现作为测试或方便的机制与 ConcurrentHashMap 一起工作。
此外,您也可以将其与分布式 Map 实现一起使用。例如,可以与 Hazelcast 配合使用。
实例化MapSessionRepository
以下示例展示了如何创建一个新的实例:<br>
SessionRepository<? extends Session> repository = new MapSessionRepository(new ConcurrentHashMap<>());
使用 Spring Session 和 Hazelcast
The Hazelcast 样例 是一个完整的应用程序,演示了如何使用 Spring Session 与 Hazelcast。
要运行它,请使用以下命令:
./gradlew :samples:hazelcast:tomcatRun
The Hazelcast Spring 样例 是一个完整的应用程序,演示了如何使用 Spring Session 与 Hazelcast 和 Spring Security。
它包括支持触发SessionCreatedEvent、SessionDeletedEvent和SessionExpiredEvent的示例Hazelcast MapListener实现。
要运行它,请使用以下命令:
./gradlew :samples:hazelcast-spring:tomcatRun
使用ReactiveMapSessionRepository
The ReactiveMapSessionRepository 允许将 Session 保存在 Map 中,其中键是 Session ID,值是 Session。
你可以使用此实现作为测试或方便机制与 ConcurrentHashMap。
或者,你也可以使用它与分布式 Map 实现中,要求提供的 Map 必须是非阻塞的。
使用JdbcIndexedSessionRepository
JdbcIndexedSessionRepository 是一个使用 Spring 的 JdbcOperations 将会话存储在关系数据库中的 SessionRepository 实现。在一个 Web 环境中,这通常与 SessionRepositoryFilter 一起使用。
请注意,此实现不支持会话事件的发布。
实例化一个JdbcIndexedSessionRepository
以下示例展示了如何创建一个新的实例:<br>
JdbcTemplate jdbcTemplate = new JdbcTemplate();
// ... configure jdbcTemplate ...
TransactionTemplate transactionTemplate = new TransactionTemplate();
// ... configure transactionTemplate ...
SessionRepository<? extends Session> repository = new JdbcIndexedSessionRepository(jdbcTemplate,
transactionTemplate);
对于如何创建和配置JdbcTemplate和PlatformTransactionManager的更多信息,请参阅Spring 框架参考文档。
使用@EnableJdbcHttpSession
在Web环境中,创建一个新的JdbcIndexedSessionRepository的最简单方式是使用@EnableJdbcHttpSession。
您可以在示例和指南(从这里开始)中找到完整的用法示例。
您可以使用以下属性来自定义配置:
-
tableName: Spring Session 使用的数据库表名,用于存储会话信息
-
maxInactiveIntervalInSeconds: 会话将在秒数内失效前的空闲时间间隔
存储详情
默认情况下,此实现使用SPRING_SESSION表和SPRING_SESSION_ATTRIBUTES表来存储会话。
请注意,您可以自定义表名,如已描述。在这种情况下,用于存储属性的表名称为提供的表名后缀_ATTRIBUTES。
如果需要进一步自定义,则可以使用set*Query个设置方法来自定义仓库使用的SQL查询。在这种情况下,您需要手动配置sessionRepository bean。
由于各个数据库提供商之间存在差异,尤其是在存储二进制数据时,请确保使用针对您所用数据库的特定 SQL 脚本。
大多数主要数据库提供商的脚本被打包为org/springframework/session/jdbc/schema-*.sql,其中*是指定的目标数据库类型。
例如,使用PostgreSQL时,您可以使用以下模式脚本:
CREATE TABLE SPRING_SESSION (
PRIMARY_ID CHAR(36) NOT NULL,
SESSION_ID CHAR(36) NOT NULL,
CREATION_TIME BIGINT NOT NULL,
LAST_ACCESS_TIME BIGINT NOT NULL,
MAX_INACTIVE_INTERVAL INT NOT NULL,
EXPIRY_TIME BIGINT NOT NULL,
PRINCIPAL_NAME VARCHAR(100),
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
);
CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_PRIMARY_ID CHAR(36) NOT NULL,
ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
ATTRIBUTE_BYTES BYTEA NOT NULL,
CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
);
使用 MySQL 数据库,您可以使用以下脚本:<br>
CREATE TABLE SPRING_SESSION (
PRIMARY_ID CHAR(36) NOT NULL,
SESSION_ID CHAR(36) NOT NULL,
CREATION_TIME BIGINT NOT NULL,
LAST_ACCESS_TIME BIGINT NOT NULL,
MAX_INACTIVE_INTERVAL INT NOT NULL,
EXPIRY_TIME BIGINT NOT NULL,
PRINCIPAL_NAME VARCHAR(100),
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_PRIMARY_ID CHAR(36) NOT NULL,
ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
ATTRIBUTE_BYTES BLOB NOT NULL,
CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
使用HazelcastIndexedSessionRepository
HazelcastIndexedSessionRepository 是一个 SessionRepository 实现,用于在 Hazelcast 的分布式 IMap 中存储会话。
在 web 环境中,这通常与 SessionRepositoryFilter 一起使用。
实例化一个HazelcastIndexedSessionRepository
以下示例展示了如何创建一个新的实例:<br>
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中删除,会导致这些事件触发发布SessionCreatedEvent、SessionExpiredEvent和SessionDeletedEvent事件(分别对应上述操作)通过ApplicationEventPublisher。
存储详情
会话存储在 Hazelcast 的分布式 IMap 中。
IMap 接口的方法用于 get() 和 put() 会话。
此外,values() 方法支持 FindByIndexNameSessionRepository#findByIndexNameAndIndexValue 操作,并配合适当的 ValueExtractor(需要注册到 Hazelcast)。有关此配置的更多详细信息,请参阅 Hazelcast Spring 示例。
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
将CookieSerializer暴露为Spring Bean可以增强您使用配置如@EnableRedisHttpSession时的现有配置。
下面的例子展示了如何做到这一点:
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("JSESSIONID"); (1)
serializer.setCookiePath("/"); (2)
serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$"); (3)
return serializer;
}
| 1 | 我们自定义cookie的名字为JSESSIONID。 |
| 2 | 我们自定义了cookie的路径为/(而不是默认的上下文根路径)。 |
| 3 | 我们自定义域名模式(正则表达式)为 ^.?\\.(\\w\\.[a-z]+)$。
这允许在不同域和应用程序之间共享会话。
如果正则表达式不匹配,则不设置域名,而是使用现有域名。
如果正则表达式匹配,则使用第一个 分组 作为域名。
这意味着对 child.example.com 的请求会将域名设置为 example.com。
然而,对 localhost:8080/ 或 192.168.1.100:8080/ 的请求不会设置 Cookie,因此在开发环境中无需任何更改即可正常工作,同时也适用于生产环境。 |
| 您应该仅匹配有效的域名字符,因为域名会在响应中被反映出来。 这样做可以防止恶意用户执行如HTTP响应拆分等攻击。 |
自定义CookieSerializer
您可以使用以下配置选项之一来自定义会话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: The value for the123. Default:Lax
| 您应该仅匹配有效的域名字符,因为域名会在响应中被反映出来。 这样做可以防止恶意用户执行如HTTP响应拆分等攻击。 |
自定义SessionRepository
实现自定义 SessionRepository API 应该是一项相当直接的任务。
将自定义实现与 @EnableSpringHttpSession 支持相结合,可让您重用现有的 Spring Session 配置功能和基础设施。
然而,有几个方面值得仔细考虑。
在HTTP请求的生命周期中,HttpSession通常会被持久化到SessionRepository两次。
第一次持久化操作是在客户端获取会话ID后确保会话可用,同时由于会在提交会话之后进行进一步修改,因此也需要在会话提交后写入。
考虑到这一点,我们一般建议SessionRepository实现跟踪更改以确保仅保存增量更新。
这对于高并发环境尤为重要,在这种环境中,多个请求可能在同一HttpSession上操作,从而导致竞态条件,一个请求覆盖另一个请求对会话属性所做的修改。
Spring Session提供的所有SessionRepository实现都使用了上述方法来持久化会话更改,并且当您自定义SessionRepository时可以作为指导。
请注意,相同的建议也适用于实现自定义 ReactiveSessionRepository。
在这种情况下,您应该使用 @EnableSpringWebSession。