|
此版本仍在开发中,尚未被认为是稳定的。请使用最新的稳定版本 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支持库都实现了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环境中,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 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 配合使用。
使用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;
使用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。