# API 文档
# 使用Session
aSession
是名称值对的简化Map
。
典型的用法可能如下所示:
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 | 我们创建一个带有泛型类型SessionRepository 的S 实例,该实例扩展Session 。泛型类型是在我们的类中定义的。 |
---|---|
2 | 我们使用SessionRepository 创建一个新的Session ,并将其分配给类型S 的变量。 |
3 | 我们与Session 交互。在我们的示例中,我们演示如何将User 保存到Session 。 |
4 | 我们现在保存Session 。这就是为什么我们需要泛型类型S 。SessionRepository 只允许保存使用相同的SessionRepository 创建或检索的Session 实例。这允许SessionRepository 进行特定于实现的优化(即只编写已更改的属性)。 |
5 | 我们从SessionRepository 中检索Session 。 |
6 | 我们从我们的Session 中获得持久的User ,而不需要显式地强制转换我们的属性。 |
Session
API 还提供与Session
实例的过期时间相关的属性。
典型的用法可能如下所示:
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 | 我们创建一个带有泛型类型SessionRepository 的S 实例,该实例扩展Session 。泛型类型是在我们的类中定义的。 |
---|---|
2 | 我们使用SessionRepository 创建一个新的Session ,并将其分配给类型S 的变量。 |
3 | 我们与Session 进行交互。在我们的示例中,我们演示了更新 Session 在过期之前可以处于非活动状态的时间。 |
4 | 我们现在保存Session 。这就是为什么我们需要泛型类型, S 。SessionRepository 只允许保存使用相同的Session 创建或检索的实例。这允许 SessionRepository 进行特定的优化(即,只写已更改的属性)。保存 Session 时,最后访问的时间将自动更新。 |
5 | 我们从SessionRepository 中检索Session 。如果 Session 过期,结果将为空。 |
# 使用SessionRepository
SessionRepository
负责创建、检索和持久化Session
实例。
如果可能,你不应该直接与SessionRepository
或Session
交互。相反,开发人员应该更喜欢与SessionRepository
和Session
通过[HttpSession
]和WebSocket集成进行间接交互。
# 使用FindByIndexNameSessionRepository
Spring 使用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 安全用户名被索引的索引名为 FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME 。 |
---|
一旦对会话进行了索引,你就可以使用类似于以下代码的代码来查找:
String username = "username";
Map<String, Session> sessionIdToSession = this.sessionRepository.findByPrincipalName(username);
# 使用ReactiveSessionRepository
aReactiveSessionRepository
负责以非阻塞和反应的方式创建、检索和持久化Session
实例。
如果可能,你不应该直接与ReactiveSessionRepository
或Session
交互。相反,你应该更喜欢与ReactiveSessionRepository
和Session
通过WebSession集成进行间接交互。
# 使用@EnableSpringHttpSession
可以将@EnableSpringHttpSession
注释添加到@Configuration
类中,以将SessionRepositoryFilter
公开为 Bean 名为springSessionRepositoryFilter
的 Bean。为了使用注释,你必须提供一个SessionRepository
Bean。下面的示例展示了如何做到这一点:
@EnableSpringHttpSession
@Configuration
public class SpringHttpSessionConfig {
@Bean
public MapSessionRepository sessionRepository() {
return new MapSessionRepository(new ConcurrentHashMap<>());
}
}
请注意,没有为你配置用于会话结束的基础架构。这是因为诸如会话过期之类的事情是高度依赖于实现的。这意味着,如果你需要清理过期的会话,那么你将负责清理过期的会话。
# 使用@EnableSpringWebSession
可以将@EnableSpringWebSession
注释添加到@Configuration
类中,以将WebSessionManager
公开为 Bean 名为webSessionManager
的 Bean。要使用注释,你必须提供一个ReactiveSessionRepository
Bean。下面的示例展示了如何做到这一点:
@EnableSpringWebSession
public class SpringWebSessionConfig {
@Bean
public ReactiveSessionRepository reactiveSessionRepository() {
return new ReactiveMapSessionRepository(new ConcurrentHashMap<>());
}
}
请注意,没有为你配置用于会话结束的基础架构。这是因为诸如会话过期之类的事情是高度依赖于实现的。这意味着,如果你需要清理过期的会话,那么你将负责清理过期的会话。
# 使用RedisIndexedSessionRepository
RedisIndexedSessionRepository
是一个SessionRepository
,它是通过使用 Spring 数据的RedisOperations
来实现的。在 Web 环境中,这通常与SessionRepositoryFilter
结合使用。该实现支持SessionDestroyedEvent
和SessionCreatedEvent
到SessionMessageListener
。
# 实例化 aRedisIndexedSessionRepository
你可以在下面的清单中看到如何创建新实例的典型示例:
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
// ... configure redisTemplate ...
SessionRepository<? extends Session> repository = new RedisIndexedSessionRepository(redisTemplate);
有关如何创建RedisConnectionFactory
的其他信息,请参见 Spring Data Redis 引用。
# 使用@EnableRedisHttpSession
在 Web 环境中,创建新的RedisIndexedSessionRepository
的最简单方法是使用@EnableRedisHttpSession
。你可以在示例和指南(从这里开始)中找到完整的示例用法。你可以使用以下属性来定制配置:
最大活动间隔秒:会话过期前的时间,以秒为单位。
重新命名空间:允许为会话配置特定于应用程序的名称空间。Redis 键和通道 ID 以
<redisNamespace>:
的前缀开始。Flushmode:允许指定何时将数据写入 Redis。只有在
SessionRepository
上调用save
时,才会出现默认值。值FlushMode.IMMEDIATE
将尽快写入 Redis。
# 自定义RedisSerializer
你可以通过创建一个名为springSessionDefaultRedisSerializer
的 Bean 来定制序列化,该 Bean 实现RedisSerializer<Object>
。
# RedisTaskExecutor
RedisIndexedSessionRepository
通过使用RedisMessageListenerContainer
订阅以接收来自 Redis 的事件。你可以通过创建一个名为springSessionRedisTaskExecutor
的 Bean、一个名为springSessionRedisSubscriptionExecutor
的 Bean,或同时创建这两个选项来定制这些事件的发送方式。你可以找到有关配置 Redis 任务执行器here (opens new window)的更多详细信息。
# 存储详细信息
以下各节概述了如何为每个操作更新 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 日午夜开始,以毫秒为单位)。
会话将在 1800 秒(30 分钟)后结束。
会话的最后一次访问是在 1404360000000(从 1970 年格林尼治标准时间 1 月 1 日午夜开始,以毫秒为单位)。
会话有两个属性。第一个是
attrName
,其值为someAttrValue
。第二个会话属性名为attrName2
,其值为someAttrValue2
。
# 优化写操作
由RedisIndexedSessionRepository
管理的Session
实例跟踪已更改的属性,并仅更新这些属性。这意味着,如果一个属性被写了一次并读了很多次,那么我们只需要写一次该属性。例如,假设更新了上一节中的 lsiting 中的attrName2
会话属性。保存后将运行以下命令:
HMSET spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe sessionAttr:attrName2 newValue
# 会话过期
根据Session.getMaxInactiveInterval()
,使用EXPIRE
命令将过期与每个会话关联。下面的示例显示了一个典型的EXPIRE
命令:
EXPIRE spring:session:sessions:33fdd1b6-b496-4b33-9f7d-df96679d32fe 2100
请注意,在会话实际到期后将过期时间设置为五分钟。这是必要的,以便在会话过期时可以访问会话的值。在会话本身实际过期五分钟后设置一个过期,以确保它被清除,但仅在我们执行任何必要的处理之后。
SessionRepository.findById(String) 方法确保不返回过期的会话。这意味着在使用会话之前不需要检查过期。 |
---|
Spring Session 依赖于从 Redis 中删除和过期的键位通知 (opens new window)来发射一个[SessionDeletedEvent
](#api-redinidexedsessionRepository-sessionDestroyedEvent)和一个[<gtr="179"/>](#api-redinidexedsessionRepository-sessionDestroyedEvent)。<gtr="180"/>或<gtr="181"/>确保清理与<gtr="182"/>相关的资源。例如,当你使用 Spring Session 的 WebSocket 支持时,Redis 过期或删除事件将触发与会话相关的任何 WebSocket 连接以关闭。
不会在会话密钥本身上直接跟踪过期,因为这将意味着会话数据将不再可用。相反,使用了一个特殊的会话过期键.在前面的示例中,Expires 键如下:
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 文档中的过期事件的时间安排 (opens new window)部分。
为了避免过期事件不一定会发生的事实,我们可以确保每个密钥在预期过期时都会被访问。这意味着,如果 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
支持在 aSession
被删除或SessionExpiredEvent
当 aSession
过期时触发SessionDeletedEvent
。这对于确保与Session
关联的资源被正确地清理是必要的。
例如,当与 WebSockets 集成时,SessionDestroyedEvent
负责关闭任何活动的 WebSocket 连接。
发射SessionDeletedEvent
或SessionExpiredEvent
是通过SessionMessageListener
提供的,后者监听Redis Keyspace 事件 (opens new window)。为了实现这一点,需要启用泛型命令和过期事件的 Redis Keyspace 事件。下面的示例展示了如何做到这一点:
redis-cli config set notify-keyspace-events Egx
如果使用@EnableRedisHttpSession
,那么管理SessionMessageListener
并启用必要的 Redis 密钥空间事件将自动完成。但是,在一个安全的 Redis 环境中,config 命令是禁用的。这意味着 Spring Session 不能为你配置 Redis Keyspace 事件。若要禁用自动配置,请将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 发送一个具有通道 IDspring:session:channel:created:33fdd1b6-b496-4b33-9f7d-df96679d32fe
的事件,其中33fdd1b6-b496-4b33-9f7d-df96679d32fe
是会话 ID。事件的主体是创建的会话。
如果注册为MessageListener
(默认值),RedisIndexedSessionRepository
然后将 Redis 消息转换为SessionCreatedEvent
。
# 查看 Redis 中的会话
在安装 Redis-CLI (opens new window)之后,你可以检查 redis使用 Redis-CLI (opens new window)中的值。例如,你可以在终端中输入以下内容:
$ 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 数据的ReactiveRedisOperations
来实现的。在 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
。你可以使用以下属性来定制配置:
最大活动间隔秒:会话过期前的时间量,以秒为单位
重新命名空间:允许为会话配置特定于应用程序的名称空间。Redis 键和通道 ID 以
<redisNamespace>:
的 Q 前缀开始。Flushmode:允许指定何时将数据写入 Redis。只有当
save
在ReactiveSessionRepository
上调用save
时,才会出现默认值。值FlushMode.IMMEDIATE
将尽快写入 Redis。
# 优化写操作
由ReactiveRedisSessionRepository
管理的Session
实例跟踪已更改的属性,并仅更新这些属性。这意味着,如果一个属性被写了一次并读了很多次,那么我们只需要写一次该属性。
# 查看 Redis 中的会话
在安装 Redis-CLI (opens new window)之后,你可以检查 redis使用 Redis-CLI (opens new window)中的值。例如,你可以在终端窗口中输入以下命令:
$ 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
,键是Session
ID,值是Session
。你可以使用带有ConcurrentHashMap
的实现作为测试或方便机制。或者,你可以将其与分布式Map
实现一起使用。例如,它可以与 Hazelcast 一起使用。
# 实例化MapSessionRepository
下面的示例展示了如何创建一个新实例:
SessionRepository<? extends Session> repository = new MapSessionRepository(new ConcurrentHashMap<>());
# 使用 Spring Session 和 hazlecast
Hazelcast 样品是一个完整的应用程序,它演示了如何使用 Spring 与 HazelCast 会话。
要运行它,请使用以下命令:
./gradlew :samples:hazelcast:tomcatRun
Hazelcast Spring Sample是一个完整的应用程序,它演示了如何使用 Spring Session 与 Spring 安全性。
它包括支持发射SessionCreatedEvent
、SessionDeletedEvent
和SessionExpiredEvent
的示例 HazelCastMapListener
实现。
要运行它,请使用以下命令:
./gradlew :samples:hazelcast-spring:tomcatRun
# 使用ReactiveMapSessionRepository
ReactiveMapSessionRepository
允许在Map
中持久化Session
,键是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);
有关如何创建和配置JdbcTemplate
和PlatformTransactionManager
的更多信息,请参见Spring Framework Reference Documentation (opens new window)。
# 使用@EnableJdbcHttpSession
在 Web 环境中,创建新的JdbcIndexedSessionRepository
的最简单方法是使用@EnableJdbcHttpSession
。你可以在示例和指南(从这里开始)中找到完整的使用示例,你可以使用以下属性来定制配置:
表格 Name: Spring Session 用于存储会话的数据库表名称
最大活动间隔秒:会话之前的时间将在几秒内到期
# 自定义LobHandler
你可以通过创建一个名为springSessionLobHandler
的 Bean 来定制 BLOB 处理,该 Bean 实现LobHandler
。
# 定制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,你可以使用以下模式脚本:
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 文档 (opens new window)。
# 使用@EnableHazelcastHttpSession
要将Hazelcast (opens new window)用作SessionRepository
的后台数据源,可以将@EnableHazelcastHttpSession
注释添加到@Configuration
类中。这样做扩展了@EnableSpringHttpSession
注释提供的功能,但在 HazelCast 中为你提供了SessionRepository
。你必须提供一个HazelcastInstance
Bean 才能使配置工作。你可以在示例和指南(从这里开始)中找到完整的配置示例。
# 基本定制
你可以在@EnableHazelcastHttpSession
上使用以下属性来定制配置:
最大活动间隔秒:会话过期前的时间,以秒为单位。默认值为 1800 秒(30 分钟)
SessionMapName:在 HazelCast 中用于存储会话数据的分布式
Map
的名称。
# 会话事件
使用MapListener
来响应从分发的Map
中添加、驱逐和删除的条目,会导致这些事件通过SessionCreatedEvent
、SessionExpiredEvent
和SessionDeletedEvent
事件分别触发ApplicationEventPublisher
事件的发布。
# 存储详细信息
会话存储在 Hazelcast 中的分布式IMap
中。IMap
接口方法用于get()
和put()
会话。此外,values()
方法支持FindByIndexNameSessionRepository#findByIndexNameAndIndexValue
操作,以及适当的ValueExtractor
(需要向 Hazelcast 注册)。有关此配置的更多详细信息,请参见 Hazelcast Spring Sample。在IMap
中的会话过期由 Hazelcast 的支持来处理,该支持将条目的生存时间设置为put()
到IMap
。空闲时间超过活动时间的条目(会话)将自动从IMap
中删除。
你不需要在 Hazelcast 配置中为IMap
配置任何设置,例如max-idle-seconds
或time-to-live-seconds
。
请注意,如果你使用 HazelCast 的MapStore
来持久化你的会话IMap
,则在重新加载MapStore
中的会话时,将应用以下限制:
重新加载触发器
EntryAddedListener
会导致SessionCreatedEvent
被重新发布对于给定的
IMap
,重新加载使用默认的 TTL 会导致会话丢失其原始 TTL
# 使用CookieSerializer
aCookieSerializer
负责定义会话 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]+)$ 。这允许跨域和应用程序共享会话。 如果正则表达式不匹配,则不设置域,并使用现有的域。 如果正则表达式匹配,第一个grouping (opens new window)被用作域。 这意味着对https://child.example.com (opens new window)的请求将域设置为 example.com 。但是,对http://localhost:8080/ (opens new window)或https://192.168.1.100:8080/ (opens new window)的请求使 cookie 未设置,因此,在开发中仍然可以工作,而不需要对生产进行任何更改。 |
你应该只匹配有效的域字符,因为响应中反映了域名。 这样做可以防止恶意用户执行HTTP 响应拆分 (opens new window)之类的攻击。 |
---|
# 定制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 域的值。如果正则表达式不匹配,则不设置域,并使用现有的域。如果正则表达式匹配,则使用第一个grouping (opens new window)作为域。sameSite
:cookie 指令SameSite
的值。要禁用SameSite
Cookie 指令的序列化,可以将该值设置为null
。默认值:Lax
你应该只匹配有效的域字符,因为响应中反映了域名。 这样做可以防止恶意用户执行HTTP 响应拆分 (opens new window)之类的攻击。 |
---|
# 自定义SessionRepository
实现自定义[SessionRepository
](#API-SessionRepository)API 应该是一项相当简单的任务。将自定义实现与[@EnableSpringHttpSession
](#api-enablespringhtpsession)支持相耦合,可以重用现有的 Spring Session 配置设施和基础设施。然而,有几个方面值得更仔细地考虑。
在 HTTP 请求的生命周期中,HttpSession
通常会两次持久化到SessionRepository
。第一个持久化操作是确保一旦客户端访问了会话 ID,会话对客户端是可用的,并且还需要在会话提交后写入,因为可能会对会话进行进一步的修改。考虑到这一点,我们通常建议SessionRepository
实现跟踪更改,以确保只保存增量。这在高度并发的环境中尤其重要,在这种环境中,多个请求在同一个HttpSession
上运行,因此会导致竞争条件,因为请求会覆盖彼此对会话属性的更改。 Spring Session 提供的所有SessionRepository
实现都使用所描述的方法来持久化会话更改,并且可以在实现自定义SessionRepository
时用于指导。
请注意,同样的建议也适用于实现自定义[ReactiveSessionRepository
](#API-reactivesessionRepository)。在这种情况下,你应该使用[@EnableSpringWebSession
]。
← Spring 安全集成 升级到 2.x →