提交 48d04a5e 编写于 作者: 茶陵後's avatar 茶陵後 👍

#19 spring session 文案优化

上级 269fc6eb
......@@ -77,7 +77,7 @@ module.exports = {
plugins: [
['autometa', autometa_options]
],
theme: path.resolve(__dirname,'./themes/theme-gitcode/index.js'),
theme: path.resolve(__dirname, './themes/theme-gitcode/index.js'),
themeConfig: {
repo: "https://gitcode.net/dev-cloud/spring",
repoLabel: "GitCode",
......@@ -266,7 +266,7 @@ module.exports = {
// initialOpenGroupIndex: 0 // 可选的, 默认值是 0
// }
// ],
// // fallback
// '/en/':
// [{
......@@ -357,29 +357,28 @@ module.exports = {
text: '更多文档',
ariaLabel: 'Others',
items: [
{ text: 'Spring Cloud Data Flow', link: '/spring-cloud-data-flow/'},
{ text: 'Spring Security', link: '/spring-security/'},
{ text: 'Spring for GraphQL', link: '/spring-for-graphql/'},
{ text: 'Spring Session', link: '/spring-session/'},
{ text: 'Spring Integration', link: '/spring-integration/'},
{ text: 'Spring HATEOAS', link: '/spring-hateoas/'},
{ text: 'Spring REST Docs', link: '/spring-rest-docs/'},
{ text: 'Spring Batch', link: '/spring-batch/'},
{ text: 'Spring AMQP', link: '/spring-amqp/'},
{ text: 'Spring CredHub', link: '/spring-credhub/'},
{ text: 'Spring Flo', link: '/spring-flo/'},
{ text: 'Spring for Apache Kafka', link: '/spring-for-apache-kafka/'},
{ text: 'Spring LDAP', link: '/spring-ldap/'},
{ text: 'Spring Shell', link: '/spring-shell/'},
{ text: 'Spring Statemachine', link: '/spring-statemachine/'},
{ text: 'Spring Vault', link: '/spring-vault/'},
{ text: 'Spring Web Flow', link: '/spring-web-flow/'},
{ text: 'Spring Web Services', link: '/spring-web-services/'}
{ text: 'Spring Cloud Data Flow', link: '/spring-cloud-data-flow/' },
{ text: 'Spring Security', link: '/spring-security/' },
{ text: 'Spring for GraphQL', link: '/spring-for-graphql/' },
{ text: 'Spring Session', link: '/spring-session/' },
{ text: 'Spring Integration', link: '/spring-integration/' },
{ text: 'Spring HATEOAS', link: '/spring-hateoas/' },
{ text: 'Spring REST Docs', link: '/spring-rest-docs/' },
{ text: 'Spring Batch', link: '/spring-batch/' },
{ text: 'Spring AMQP', link: '/spring-amqp/' },
{ text: 'Spring CredHub', link: '/spring-credhub/' },
{ text: 'Spring Flo', link: '/spring-flo/' },
{ text: 'Spring for Apache Kafka', link: '/spring-for-apache-kafka/' },
{ text: 'Spring LDAP', link: '/spring-ldap/' },
{ text: 'Spring Shell', link: '/spring-shell/' },
{ text: 'Spring Statemachine', link: '/spring-statemachine/' },
{ text: 'Spring Vault', link: '/spring-vault/' },
{ text: 'Spring Web Flow', link: '/spring-web-flow/' },
{ text: 'Spring Web Services', link: '/spring-web-services/' }
]
}
],
],
sidebar: {
'/spring-boot/': [
{
title: 'Spring Boot 文档',
......@@ -510,6 +509,108 @@ module.exports = {
initialOpenGroupIndex: 0 // 可选的, 默认值是 0
}
],
'/spring-session/': [
{
title: 'Spring Session 文档',
sidebarDepth: 2,
collapsable: false,
children: [
"/spring-session/_index.md",
"/spring-session/whats-new.md",
"/spring-session/samples.md",
"/spring-session/bootSamples/HttpSession/mongo.md",
"/spring-session/bootSamples/HttpSession/jdbc.md",
"/spring-session/bootSamples/HttpSession/Redis/boot-redis.md",
"/spring-session/bootSamples/boot-findbyusername.md",
"/spring-session/bootSamples/boot-websocket.md",
"/spring-session/webFlux/boot-webflux-custom-cookie.md",
"/spring-session/modules.md",
"/spring-session/http-session.md",
"/spring-session/web-socket.md",
"/spring-session/web-session.md",
"/spring-session/spring-security.md",
"/spring-session/api.md",
"/spring-session/upgrading.md"
],
initialOpenGroupIndex: 0 // 可选的, 默认值是 0
}
],
'/spring-session/sample/': [
{
title: 'Spring Session 文档3',
sidebarDepth: 2,
collapsable: false,
children: [
"/spring-session/sample/samples.md"
],
initialOpenGroupIndex: 0 // 可选的, 默认值是 0
}
],
'/spring-integration/': [
{
title: 'Spring Integration 文档',
sidebarDepth: 2,
collapsable: false,
children: [
"/spring-integration/preface.md",
"/spring-integration/whats-new.md",
"/spring-integration/overview.md",
"/spring-integration/core.md",
"/spring-integration/message.md",
"/spring-integration/message-routing.md",
"/spring-integration/message-transformation.md",
"/spring-integration/messaging-endpoints.md",
"/spring-integration/dsl.md",
"/spring-integration/kotlin-dsl.md",
"/spring-integration/system-management.md",
"/spring-integration/reactive-streams.md",
"/spring-integration/endpoint-summary.md",
"/spring-integration/amqp.md",
"/spring-integration/event.md",
"/spring-integration/feed.md",
"/spring-integration/file.md",
"/spring-integration/ftp.md",
"/spring-integration/gemfire.md",
"/spring-integration/http.md",
"/spring-integration/jdbc.md",
"/spring-integration/jpa.md",
"/spring-integration/jms.md",
"/spring-integration/jmx.md",
"/spring-integration/kafka.md",
"/spring-integration/mail.md",
"/spring-integration/mongodb.md",
"/spring-integration/mqtt.md",
"/spring-integration/r2dbc.md",
"/spring-integration/redis.md",
"/spring-integration/resource.md",
"/spring-integration/rmi.md",
"/spring-integration/rsocket.md",
"/spring-integration/sftp.md",
"/spring-integration/stomp.md",
"/spring-integration/stream.md",
"/spring-integration/syslog.md",
"/spring-integration/ip.md",
"/spring-integration/webflux.md",
"/spring-integration/web-sockets.md",
"/spring-integration/ws.md",
"/spring-integration/xml.md",
"/spring-integration/xmpp.md",
"/spring-integration/zeromq.md",
"/spring-integration/zookeeper.md",
"/spring-integration/error-handling.md",
"/spring-integration/spel.md",
"/spring-integration/message-publishing.md",
"/spring-integration/transactions.md",
"/spring-integration/security.md",
"/spring-integration/configuration.md",
"/spring-integration/testing.md",
"/spring-integration/samples.md",
"/spring-integration/resources.md",
"/spring-integration/history.md"
],
initialOpenGroupIndex: 0 // 可选的, 默认值是 0
}
],
'/spring-hateoas/': [
{
title: 'Spring HATEOAS 文档',
......@@ -687,7 +788,7 @@ module.exports = {
}
],
// fallback
'/':
'/':
[{
title: 'Spring 文档', // 必要的
// path: '/', // 可选的, 标题的跳转链接,应为绝对路径且必须存在
......@@ -740,7 +841,7 @@ module.exports = {
],
initialOpenGroupIndex: 0 // 可选的, 默认值是 0
}
]
]
}
}
}
......
# Spring Session
\ No newline at end of file
# Spring 会议
Spring Session 提供了用于管理用户的会话信息的 API 和实现。
Spring Session 提供了用于管理用户会话信息的 API 和实现,同时也使得在不绑定到特定于应用程序容器的解决方案的情况下支持群集会话变得非常简单。它还提供了以下方面的透明整合:
* [HttpSession ](http-session.html#httpsession):允许以与应用程序容器无关的方式替换`HttpSession`,并支持在头文件中提供会话 ID,以便与 RESTful API 一起工作。
* [WebSocket](web-socket.html#websocket):提供了在接收 WebSocket 消息时保持`HttpSession`活动的能力
* [WebSession](web-session.html#websession):允许以与应用程序容器无关的方式替换 Spring WebFlux 的`WebSession`
## Spring Session 社区
我们 GLAD 将你视为我们社区的一部分。以下各节提供了有关如何与 Spring Session 社区交互的更多信息。
### 支持
你可以通过在[stack overflow with`spring-session`tag](https://stackoverflow.com/questions/tagged/ Spring-session)上提问来获得帮助。同样,我们通过回答有关 Stack Overflow 的问题来鼓励帮助他人。
### 源代码
你可以在 Github 上找到源代码,网址为[https://github.com/spring-projects/spring-session/](https://github.com/spring-projects/spring-session/)
### 问题跟踪
我们在[https://github.com/spring-projects/spring-session/issues](https://github.com/spring-projects/spring-session/issues)上跟踪 GitHub 问题
### 贡献
我们感谢[拉请求](https://help.github.com/articles/using-pull-requests/)
### 许可证
Spring Session 是在[Apache2.0 许可证](https://www.apache.org/licenses/LICENSE-2.0)下发布的开源软件。
### 社区扩展
| Name |位置|
|-------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|Spring Session Infinispan|[https://infinispan.org/infinispan-spring-boot/master/spring\_boot\_starter.html#\_enabling\_spring\_session\_support](https://infinispan.org/infinispan-spring-boot/master/spring_boot_starter.html#_enabling_spring_session_support)|
## 最低要求
Spring 场会议的最低要求是:
* Java8+。
* 如果你在 Servlet 容器中运行(不是必需的),则 Servlet 3.1+。
* 如果使用其他 Spring 库(不是必需的),则所需的最低版本是 Spring 5.0.x。
* `@EnableRedisHttpSession`需要 Redis2.8+。这是支持[会话过期](api.html#api-redisindexedsessionrepository-expiration)所必需的
* `@EnableHazelcastHttpSession`需要 HazelCast3.6+。这是支持[`FindByIndexNameSessionRepository`]所必需的(api.html#api-enablehazelcasthtpsession-storage)
| |在其核心, Spring Session 仅对`spring-jcl`具有所需的依赖关系。<br/>关于使用 Spring Session 而不使用任何其他 Spring 依赖关系的示例,请参见[Hazelcast 样品](samples.html#samples)应用程序。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
\ No newline at end of file
# API 文档
## 使用`Session`
a`Session`是名称值对的简化`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`进行交互。<br/>在我们的示例中,我们演示了更新`Session`在过期之前可以处于非活动状态的时间。|
|**4**|我们现在保存`Session`<br/>这就是为什么我们需要泛型类型,`S`<br/>`SessionRepository`只允许保存使用相同的`Session`创建或检索的实例。<br/>这允许`SessionRepository`进行特定的优化(即,只写已更改的属性)。<br/>保存`Session`时,最后访问的时间将自动更新。|
|**5**|我们从`SessionRepository`中检索`Session`<br/>如果`Session`过期,结果将为空。|
## 使用`SessionRepository`
`SessionRepository`负责创建、检索和持久化`Session`实例。
如果可能,你不应该直接与`SessionRepository``Session`交互。相反,开发人员应该更喜欢与`SessionRepository``Session`通过[`HttpSession`]和[WebSocket](web-socket.html#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`的一些实现方式提供钩子来自动索引其他会话属性。<br/>例如,许多实现方式自动地确保当前 Spring 安全用户名被索引的索引名为`FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME`。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
一旦对会话进行了索引,你就可以使用类似于以下代码的代码来查找:
```
String username = "username";
Map<String, Session> sessionIdToSession = this.sessionRepository.findByPrincipalName(username);
```
## 使用`ReactiveSessionRepository`
a`ReactiveSessionRepository`负责以非阻塞和反应的方式创建、检索和持久化`Session`实例。
如果可能,你不应该直接与`ReactiveSessionRepository``Session`交互。相反,你应该更喜欢与`ReactiveSessionRepository``Session`通过[WebSession](web-session.html#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`
### 实例化 a`RedisIndexedSessionRepository`
你可以在下面的清单中看到如何创建新实例的典型示例:
```
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
// ... configure redisTemplate ...
SessionRepository<? extends Session> repository = new RedisIndexedSessionRepository(redisTemplate);
```
有关如何创建`RedisConnectionFactory`的其他信息,请参见 Spring Data Redis 引用。
### 使用`@EnableRedisHttpSession`
在 Web 环境中,创建新的`RedisIndexedSessionRepository`的最简单方法是使用`@EnableRedisHttpSession`。你可以在[示例和指南(从这里开始)](samples.html#samples)中找到完整的示例用法。你可以使用以下属性来定制配置:
* **最大活动间隔秒**:会话过期前的时间,以秒为单位。
* **重新命名空间**:允许为会话配置特定于应用程序的名称空间。Redis 键和通道 ID 以`<redisNamespace>:`的前缀开始。
* **Flushmode**:允许指定何时将数据写入 Redis。只有在`SessionRepository`上调用`save`时,才会出现默认值。值`FlushMode.IMMEDIATE`将尽快写入 Redis。
#### 自定义`RedisSerializer`
你可以通过创建一个名为`springSessionDefaultRedisSerializer`的 Bean 来定制序列化,该 Bean 实现`RedisSerializer<Object>`
### Redis`TaskExecutor`
`RedisIndexedSessionRepository`通过使用`RedisMessageListenerContainer`订阅以接收来自 Redis 的事件。你可以通过创建一个名为`springSessionRedisTaskExecutor`的 Bean、一个名为`springSessionRedisSubscriptionExecutor`的 Bean,或同时创建这两个选项来定制这些事件的发送方式。你可以找到有关配置 Redis 任务执行器[here](https://docs.spring.io/spring-data-redis/docs/2.6.2/reference/html/#redis:pubsub:subscribe:containers)的更多详细信息。
### 存储详细信息
以下各节概述了如何为每个操作更新 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)`方法确保不返回过期的会话。<br/>这意味着在使用会话之前不需要检查过期。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
Spring Session 依赖于从 Redis 中删除和过期的[键位通知](https://redis.io/topics/notifications)来发射一个[`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 文档中的[过期事件的时间安排](https://redis.io/topics/notifications)部分。
为了避免过期事件不一定会发生的事实,我们可以确保每个密钥在预期过期时都会被访问。这意味着,如果 TTL 在密钥上过期,则 Redis 将删除该密钥,并在尝试访问该密钥时触发过期事件。
由于这个原因,每个会话的到期时间也被跟踪到最近的分钟。这使后台任务能够访问可能过期的会话,以确保以更确定的方式触发 REDIS 过期事件。下面的示例显示了这些事件:
```
SADD spring:session:expirations:1439245080000 expires:33fdd1b6-b496-4b33-9f7d-df96679d32fe
EXPIRE spring:session:expirations1439245080000 2100
```
然后,后台任务使用这些映射显式地请求每个键。通过访问密钥,而不是删除它,我们确保 Redis 仅在 TTL 过期时为我们删除密钥。
| |我们不会显式地删除这些密钥,因为在某些情况下,可能存在一个竞争条件,当某个密钥没有过期时,该条件会错误地将其标识为过期,<br/>不使用分布式锁(这会影响我们的性能),没有办法确保过期映射的一致性。<br/>通过简单地访问该密钥,我们确保只有当该密钥上的 TTL 过期时才会删除该密钥。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
### `SessionDeletedEvent`和`SessionExpiredEvent`
`SessionDeletedEvent``SessionExpiredEvent`都是`SessionDestroyedEvent`的类型。
`RedisIndexedSessionRepository`支持在 a`Session`被删除或`SessionExpiredEvent`当 a`Session`过期时触发`SessionDeletedEvent`。这对于确保与`Session`关联的资源被正确地清理是必要的。
例如,当与 WebSockets 集成时,`SessionDestroyedEvent`负责关闭任何活动的 WebSocket 连接。
发射`SessionDeletedEvent``SessionExpiredEvent`是通过`SessionMessageListener`提供的,后者监听[Redis Keyspace 事件](https://redis.io/topics/notifications)。为了实现这一点,需要启用泛型命令和过期事件的 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 发送一个具有通道 ID`spring:session:channel:created:33fdd1b6-b496-4b33-9f7d-df96679d32fe`的事件,其中`33fdd1b6-b496-4b33-9f7d-df96679d32fe`是会话 ID。事件的主体是创建的会话。
如果注册为`MessageListener`(默认值),`RedisIndexedSessionRepository`然后将 Redis 消息转换为`SessionCreatedEvent`
### 查看 Redis 中的会话
[安装 Redis-CLI](https://redis.io/topics/quickstart)之后,你可以检查 redis[使用 Redis-CLI](https://redis.io/commands#hash)中的值。例如,你可以在终端中输入以下内容:
```
$ 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](https://redis.io/topics/quickstart)之后,你可以检查 redis[使用 Redis-CLI](https://redis.io/commands#hash)中的值。例如,你可以在终端窗口中输入以下命令:
```
$ 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 样品](samples.html#samples)是一个完整的应用程序,它演示了如何使用 Spring 与 HazelCast 会话。
要运行它,请使用以下命令:
```
./gradlew :samples:hazelcast:tomcatRun
```
[Hazelcast Spring Sample](samples.html#samples)是一个完整的应用程序,它演示了如何使用 Spring Session 与 Spring 安全性。
它包括支持发射`SessionCreatedEvent``SessionDeletedEvent``SessionExpiredEvent`的示例 HazelCast`MapListener`实现。
要运行它,请使用以下命令:
```
./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](https://docs.spring.io/spring/docs/5.3.16/spring-framework-reference/data-access.html)
### 使用`@EnableJdbcHttpSession`
在 Web 环境中,创建新的`JdbcIndexedSessionRepository`的最简单方法是使用`@EnableJdbcHttpSession`。你可以在[示例和指南(从这里开始)](samples.html#samples)中找到完整的使用示例,你可以使用以下属性来定制配置:
* **表格 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 文档](https://docs.hazelcast.org/docs/3.12.12/manual/html-single/index.html#hazelcast-configuration)
### 使用`@EnableHazelcastHttpSession`
要将[Hazelcast](https://hazelcast.org/)用作`SessionRepository`的后台数据源,可以将`@EnableHazelcastHttpSession`注释添加到`@Configuration`类中。这样做扩展了`@EnableSpringHttpSession`注释提供的功能,但在 HazelCast 中为你提供了`SessionRepository`。你必须提供一个`HazelcastInstance` Bean 才能使配置工作。你可以在[示例和指南(从这里开始)](samples.html#samples)中找到完整的配置示例。
### 基本定制
你可以在`@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](samples.html#samples)。在`IMap`中的会话过期由 Hazelcast 的支持来处理,该支持将条目的生存时间设置为`put()``IMap`。空闲时间超过活动时间的条目(会话)将自动从`IMap`中删除。
你不需要在 Hazelcast 配置中为`IMap`配置任何设置,例如`max-idle-seconds``time-to-live-seconds`
请注意,如果你使用 HazelCast 的`MapStore`来持久化你的会话`IMap`,则在重新加载`MapStore`中的会话时,将应用以下限制:
* 重新加载触发器`EntryAddedListener`会导致`SessionCreatedEvent`被重新发布
* 对于给定的`IMap`,重新加载使用默认的 TTL 会导致会话丢失其原始 TTL
## 使用`CookieSerializer`
a`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]+)$`<br/>这允许跨域和应用程序共享会话。<br/>如果正则表达式不匹配,则不设置域,并使用现有的域。<br/>如果正则表达式匹配,第一个[grouping](https://docs.oracle.com/javase/tutorial/essential/regex/groups.html)被用作域。<br/>这意味着对[https://child.example.com](https://child.example.com)的请求将域设置为`example.com`<br/>但是,对[http://localhost:8080/](http://localhost:8080/)[https://192.168.1.100:8080/](https://192.168.1.100:8080/)的请求使 cookie 未设置,因此,在开发中仍然可以工作,而不需要对生产进行任何更改。|
| |你应该只匹配有效的域字符,因为响应中反映了域名。<br/>这样做可以防止恶意用户执行[HTTP 响应拆分](https://en.wikipedia.org/wiki/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 域的值。如果正则表达式不匹配,则不设置域,并使用现有的域。如果正则表达式匹配,则使用第一个[grouping](https://docs.oracle.com/javase/tutorial/essential/regex/groups.html)作为域。
* `sameSite`:cookie 指令`SameSite`的值。要禁用`SameSite`Cookie 指令的序列化,可以将该值设置为`null`。默认值:`Lax`
| |你应该只匹配有效的域字符,因为响应中反映了域名。<br/>这样做可以防止恶意用户执行[HTTP 响应拆分](https://en.wikipedia.org/wiki/HTTP_response_splitting)之类的攻击。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
## 自定义`SessionRepository`
实现自定义[`SessionRepository`](#API-SessionRepository)API 应该是一项相当简单的任务。将自定义实现与[`@EnableSpringHttpSession`](#api-enablespringhtpsession)支持相耦合,可以重用现有的 Spring Session 配置设施和基础设施。然而,有几个方面值得更仔细地考虑。
在 HTTP 请求的生命周期中,`HttpSession`通常会两次持久化到`SessionRepository`。第一个持久化操作是确保一旦客户端访问了会话 ID,会话对客户端是可用的,并且还需要在会话提交后写入,因为可能会对会话进行进一步的修改。考虑到这一点,我们通常建议`SessionRepository`实现跟踪更改,以确保只保存增量。这在高度并发的环境中尤其重要,在这种环境中,多个请求在同一个`HttpSession`上运行,因此会导致竞争条件,因为请求会覆盖彼此对会话属性的更改。 Spring Session 提供的所有`SessionRepository`实现都使用所描述的方法来持久化会话更改,并且可以在实现自定义`SessionRepository`时用于指导。
请注意,同样的建议也适用于实现自定义[`ReactiveSessionRepository`](#API-reactivesessionRepository)。在这种情况下,你应该使用[`@EnableSpringWebSession`]。
\ No newline at end of file
# Spring Session- Spring Boot
本指南描述了在使用 Spring 引导时如何使用 Spring Session 透明地利用 Redis 来支持 Web 应用程序的`HttpSession`
| |你可以在[引导示例应用程序](#boot-sample)中找到完整的指南。|
|---|--------------------------------------------------------------------------------|
[Index](../index.html)
## 更新依赖项
在使用 Spring Session 之前,你必须确保你的依赖关系。我们假设你正在使用一个有效的启动 Web 应用程序。如果正在使用 Maven,则必须添加以下依赖项:
POM.xml
```
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
</dependencies>
```
Spring 启动为 Spring Session 模块提供了依赖管理,因此不需要显式声明依赖版本。
## Spring 引导配置
在添加了所需的依赖项之后,我们就可以创建我们的 Spring 启动配置了。多亏了一流的自动配置支持,由 Redis 支持的设置 Spring Session 非常简单,只需向你的`application.properties`添加一个配置属性,如以下清单所示:
SRC/主/资源/应用程序.properties
```
spring.session.store-type=redis # Session store type.
```
在这种情况下, Spring boot 应用的配置相当于手动添加`@EnableRedisHttpSession`注释。这将创建一个名为`springSessionRepositoryFilter`的 Spring Bean,实现`Filter`。过滤器负责替换要由 Spring Session 支持的`HttpSession`实现。
使用`application.properties`还可以进行进一步的定制,如以下清单所示:
SRC/主/资源/应用程序.properties
```
server.servlet.session.timeout= # Session timeout. If a duration suffix is not specified, seconds is used.
spring.session.redis.flush-mode=on_save # Sessions flush mode.
spring.session.redis.namespace=spring:session # Namespace for keys used to store sessions.
```
有关更多信息,请参见 Spring 引导文档的[Spring Session](https://docs.spring.io/spring-boot/docs/2.5.6/reference/htmlsingle/#boot-features-session)部分。
## 配置 Redis 连接
Spring 启动会自动创建一个`RedisConnectionFactory`,它将 Spring Session 连接到端口 6379(默认端口)上本地主机上的 Redis 服务器。在生产环境中,你需要更新配置以指向 Redis 服务器。例如,你可以在应用程序中包含以下内容:
SRC/主/资源/应用程序.properties
```
spring.redis.host=localhost # Redis server host.
spring.redis.password= # Login password of the redis server.
spring.redis.port=6379 # Redis server port.
```
有关更多信息,请参见 Spring 引导文档的[连接到 Redis](https://docs.spring.io/spring-boot/docs/2.5.6/reference/htmlsingle/#boot-features-connecting-to-redis)部分。
## Servlet 容器初始化
我们的[Spring Boot Configuration](#boot-spring-configuration)创建了一个名为`springSessionRepositoryFilter`的 Spring Bean,它实现了`Filter``springSessionRepositoryFilter` Bean 负责用 Spring Session 支持的自定义实现替换`HttpSession`
为了使我们的`Filter`发挥其魔力, Spring 需要加载我们的`Config`类。最后,我们需要确保我们的 Servlet 容器(即 Tomcat)为每个请求使用我们的`springSessionRepositoryFilter`。幸运的是,Boot 为我们解决了这两个步骤。
## 引导示例应用程序
引导示例应用程序演示了如何在使用 Spring 引导时使用 Spring Session 透明地利用 Redis 来支持 Web 应用程序的`HttpSession`
### 运行引导示例应用程序
你可以通过获取[源代码](https://github.com/spring-projects/spring-session/archive/main.zip)并调用以下命令来运行示例:
```
$ ./gradlew :spring-session-sample-boot-redis:bootRun
```
| |要使示例工作,你必须在 localhost 上[安装 Redis2.8+](https://redis.io/download)并使用默认端口(6379)运行它。<br/>或者,你可以更新`RedisConnectionFactory`以指向 Redis 服务器。<br/>另一个选项是使用[Docker](https://www.docker.com/)在 localhost 上运行 Redis。详细说明见[Docker Redis 存储库](https://hub.docker.com/_/redis/)。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
现在你应该可以在[http://localhost:8080/](http://localhost:8080/)上访问应用程序了。
### 探索`security`示例应用程序
现在你可以尝试使用该应用程序了。输入以下内容即可登录:
* **用户 Name** *User*
* **密码** *密码*
现在点击**登录**按钮。你现在应该会看到一条消息,表明你是用先前输入的用户登录的。用户的信息存储在 Redis 中,而不是 Tomcat 的`HttpSession`实现中。
### 它是如何工作的?
我们不使用 Tomcat 的`HttpSession`,而是在 Redis 中保存这些值。 Spring Session 用一个由 Redis 支持的实现替换`HttpSession`。当 Spring Security 的`SecurityContextPersistenceFilter``SecurityContext`保存到`HttpSession`时,它将被持久化到 Redis 中。
当创建一个新的`HttpSession`时, Spring Session 将在浏览器中创建一个名为`SESSION`的 cookie。该 cookie 包含你的会话的 ID。你可以查看 cookies(使用[Chrome](https://developers.google.com/web/tools/chrome-devtools/manage-data/cookies)[Firefox](https://developer.mozilla.org/en-US/docs/Tools/Storage_Inspector))。
你可以使用 redis-cli 删除会话。例如,在基于 Linux 的系统上,你可以键入以下内容:
```
$ redis-cli keys '*' | xargs redis-cli del
```
| |Redis 文档中有[安装 Redis-CLI](https://redis.io/topics/quickstart)的说明。|
|---|--------------------------------------------------------------------------------------------------------|
或者,你也可以删除显式密钥。要这样做,请在终端中输入以下内容,并确保将`7e8383a4-082c-4ffe-a4bc-c40fd3363c5e`替换为`SESSION`cookie 的值:
```
$ redis-cli del spring:session:sessions:7e8383a4-082c-4ffe-a4bc-c40fd3363c5e
```
现在,你可以访问[http://localhost:8080/](http://localhost:8080/)上的应用程序,并观察到我们不再经过身份验证。
\ No newline at end of file
# Spring Session- Spring 启动
本指南描述了在使用 Spring 引导时如何使用 Spring Session 来透明地利用关系数据库来支持 Web 应用程序的`HttpSession`
| |你可以在[httpsession-jdbc-boot 示例应用程序](#httpsession-jdbc-boot-sample)中找到完整的指南。|
|---|------------------------------------------------------------------------------------------------------------------|
[Index](../index.html)
## 更新依赖项
在使用 Spring Session 之前,你必须更新你的依赖关系。我们假设你正在使用一个有效的启动 Web 应用程序。如果使用 Maven,则必须添加以下依赖项:
POM.xml
```
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-jdbc</artifactId>
</dependency>
</dependencies>
```
Spring 启动为 Spring Session 模块提供了依赖管理,因此不需要显式地声明依赖版本。
## Spring 引导配置
在添加了所需的依赖项之后,我们就可以创建我们的 Spring 启动配置了。多亏了一流的自动配置支持,在关系数据库支持下设置 Spring Session 就像在`application.properties`中添加一个配置属性一样简单。下面的清单展示了如何做到这一点:
SRC/主/资源/应用程序.properties
```
spring.session.store-type=jdbc # Session store type.
```
如果在 Classpath 上存在单个 Spring Session 模块,则 Spring 引导将自动使用该存储实现。如果有多个实现,则必须选择要用来存储会话的 StoreType,如上面所示。
在这种情况下, Spring boot 应用的配置相当于手动添加`@EnableJdbcHttpSession`注释。这将创建一个名为`springSessionRepositoryFilter`的 Spring Bean。 Bean 实现`Filter`。过滤器负责替换要由 Spring Session 支持的`HttpSession`实现。
你可以使用`application.properties`来进一步自定义。下面的清单展示了如何做到这一点:
SRC/主/资源/应用程序.properties
```
server.servlet.session.timeout= # Session timeout. If a duration suffix is not specified, seconds are used.
spring.session.jdbc.initialize-schema=embedded # Database schema initialization mode.
spring.session.jdbc.schema=classpath:org/springframework/session/jdbc/[email protected]@[email protected]@.sql # Path to the SQL file to use to initialize the database schema.
spring.session.jdbc.table-name=SPRING_SESSION # Name of the database table used to store sessions.
```
有关更多信息,请参见 Spring 引导文档的[Spring Session](https://docs.spring.io/spring-boot/docs/2.5.6/reference/htmlsingle/#boot-features-session)部分。
## 配置`DataSource`
Spring 引导会自动创建一个`DataSource`,它将 Spring Session 连接到 H2 数据库的嵌入式实例。在生产环境中,你需要更新配置以指向关系数据库。例如,你可以在应用程序中包含以下内容:
SRC/主/资源/应用程序.properties
```
spring.datasource.url= # JDBC URL of the database.
spring.datasource.username= # Login username of the database.
spring.datasource.password= # Login password of the database.
```
有关更多信息,请参见 Spring 引导文档的[配置数据源](https://docs.spring.io/spring-boot/docs/2.5.6/reference/htmlsingle/#boot-features-configure-datasource)部分。
## Servlet 容器初始化
我们的[Spring Boot Configuration](#httpsession-jdbc-boot-spring-configuration)创建了一个名为`springSessionRepositoryFilter`的 Spring Bean,它实现了`Filter``springSessionRepositoryFilter` Bean 负责用 Spring Session 支持的自定义实现替换`HttpSession`
为了让我们的`Filter`发挥其魔力, Spring 需要加载我们的`Config`类。最后,我们需要确保我们的 Servlet 容器(即 Tomcat)为每个请求使用我们的`springSessionRepositoryFilter`。幸运的是,Boot 为我们解决了这两个步骤。
## `httpsession-jdbc-boot`示例应用程序
HttpSession-JDBC-Boot 示例应用程序演示了如何在使用 Spring 引导时使用 Spring Session 透明地利用 H2 数据库来支持 Web 应用程序的`HttpSession`
### 运行`httpsession-jdbc-boot`示例应用程序
你可以通过获取[源代码](https://github.com/spring-projects/spring-session/archive/main.zip)并调用以下命令来运行示例:
```
$ ./gradlew :spring-session-sample-boot-jdbc:bootRun
```
现在你应该可以在[http://localhost:8080/](http://localhost:8080/)上访问应用程序了。
### 探索安全示例应用程序
你现在可以尝试使用该应用程序了。要这样做,请输入以下内容以进行登录:
* **用户 Name** *User*
* **密码** *密码*
现在点击**登录**按钮。你现在应该会看到一条消息,该消息指示你是用先前输入的用户登录的。用户的信息存储在 H2 数据库中,而不是 Tomcat 的`HttpSession`实现。
### 它是如何工作的?
我们不使用 Tomcat 的`HttpSession`,而是将这些值保存在 H2 数据库中。 Spring Session 使用由关系数据库支持的实现来替换`HttpSession`。当 Spring Security 的`SecurityContextPersistenceFilter``SecurityContext`保存到`HttpSession`时,它将被持久化到 H2 数据库中。
当创建一个新的`HttpSession`时, Spring Session 将在浏览器中创建一个名为`SESSION`的 cookie。该 cookie 包含你的会话的 ID。你可以查看 cookies(使用[Chrome](https://developers.google.com/web/tools/chrome-devtools/manage-data/cookies)[Firefox](https://developer.mozilla.org/en-US/docs/Tools/Storage_Inspector))。
你可以通过使用 H2Web 控制台来删除会话:[http://localhost:8080/h2-console/](http://localhost:8080/h2-console/)(对于 JDBC URL 使用`jdbc:h2:mem:testdb`)。
现在,你可以访问[http://localhost:8080/](http://localhost:8080/)上的应用程序,并看到我们不再经过身份验证。
\ No newline at end of file
# Spring Session-MongoDB 存储库
本指南描述了如何使用由 MongoDB 支持的 Spring Session 。
| |完整的指南可在[Mongo 示例应用程序](#mongo-sample)中找到。|
|---|----------------------------------------------------------------------------------|
[Index](../index.html)
## 更新依赖项
在使用 Spring Session MongoDB 之前,必须确保更新依赖项。我们假设你正在使用一个有效的启动 Web 应用程序。如果你正在使用 Maven,请确保添加以下依赖项:
POM.xml
```
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-mongodb</artifactId>
</dependency>
</dependencies>
```
由于我们使用的是快照版本,因此我们需要确保添加 Spring 快照 Maven 存储库。确保在 POM.xml 中包含以下内容:
POM.xml
```
<repositories>
<!-- ... -->
<repository>
<id>spring-snapshot</id>
<url>https://repo.spring.io/libs-snapshot</url>
</repository>
</repositories>
```
## Spring 配置
在添加了所需的依赖关系之后,我们就可以创建我们的 Spring 配置了。 Spring 配置负责创建一个 Servlet 过滤器,该过滤器将`HttpSession`实现替换为由 Spring Session 支持的实现。
你所要做的就是添加以下 Spring 配置:
```
@EnableMongoHttpSession (1)
public class HttpSessionConfig {
@Bean
public JdkMongoSessionConverter jdkMongoSessionConverter() {
return new JdkMongoSessionConverter(Duration.ofMinutes(30)); (2)
}
}
```
|**1**|`@EnableMongoHttpSession`注释创建了一个名为`springSessionRepositoryFilter`的 Spring Bean,它实现了 filter。<br/>这个 filter 用 MongoDB 支持的 Bean 替换了默认的`HttpSession`。|
|-----|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|将会话超时时间配置为 30 分钟。|
## 配置 MongoDB 连接
Spring 引导会自动创建一个`MongoClient`,它将 Spring Session 连接到端口 27017(默认端口)上的 LocalHost 上的 MongoDB 服务器。在生产环境中,你需要确保更新配置以指向 MongoDB 服务器。例如,你可以在**应用程序.属性**中包含以下内容
SRC/主/资源/应用程序.properties
```
spring.data.mongodb.host=mongo-srv
spring.data.mongodb.port=27018
spring.data.mongodb.database=prod
```
有关更多信息,请参阅 Spring 引导文档的[连接到 MongoDB](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-connecting-to-mongodb)部分。
## Servlet 容器初始化
我们的[Spring Configuration](#boot-mongo-configuration)创建了一个名为`springSessionRepositoryFilter`的 Spring Bean,它实现了`Filter``springSessionRepositoryFilter` Bean 负责用 Spring Session 支持的自定义实现替换`HttpSession`
为了让我们的`Filter`发挥其魔力, Spring 需要加载我们的`Config`类。最后,我们需要确保我们的 Servlet 容器(即 Tomcat)对每个请求使用我们的`springSessionRepositoryFilter`。幸运的是,Boot 为我们解决了这两个步骤。
## MongoDB 示例应用程序
MongoDB 示例应用程序演示了如何在使用 Spring 引导时使用 Spring Session 透明地利用 MongoDB 来支持 Web 应用程序的`HttpSession`
### 运行 MongoDB 示例应用程序
你可以通过获取[源代码](https://github.com/spring-projects/spring-session/archive/main.zip)并调用以下命令来运行示例:
```
$ ./gradlew :samples:mongo:bootRun
```
现在你应该可以在[http://localhost:8080/](http://localhost:8080/)上访问应用程序了。
### 探索安全示例应用程序
尝试使用该应用程序。输入以下内容即可登录:
* **用户 Name** *User*
* **密码** *密码*
现在点击**登录**按钮。你现在应该会看到一条消息,表明你是用先前输入的用户登录的。用户的信息存储在 MongoDB 中,而不是 Tomcat 的`HttpSession`实现中。
### 它是如何工作的?
而不是使用 Tomcat 的`HttpSession`,我们实际上是在 Mongo 中持久化这些值。 Spring Session 用 Mongo 支持的实现替换`HttpSession`。当 Spring Security 的`SecurityContextPersistenceFilter``SecurityContext`保存到`HttpSession`时,它将被持久化到 Mongo 中。
当创建一个新的`HttpSession`时, Spring Session 将在浏览器中创建一个名为会话的 cookie,其中包含会话的 ID。继续查看 cookies(单击[Chrome](https://developer.chrome.com/devtools/docs/resources#cookies)[Firefox](https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List)以获取帮助)。
如果你愿意,你可以使用 Mongo 客户机轻松地检查会话。例如,在基于 Linux 的系统上,你可以键入:
| |示例应用程序使用了一个嵌入式 MongoDB 实例,该实例监听随机分配的端口。<br/>嵌入式 MongoDB 使用的端口以及用于连接到它的 Exact 命令在应用程序启动时记录。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
```
$ mongo --port ...
> use test
> db.sessions.find().pretty()
```
或者,你也可以删除显式密钥。在终端中输入以下内容,以确保用会话 cookie 的值替换`60f17293-839b-477c-bb92-07a9c3658843`:
```
> db.sessions.remove({"_id":"60f17293-839b-477c-bb92-07a9c3658843"})
```
现在访问[http://localhost:8080/](http://localhost:8080/)上的应用程序,并观察到我们不再经过身份验证。
\ No newline at end of file
# Spring Session-按用户名查找
本指南描述了如何使用 Spring Session 通过用户名查找会话。
| |你可以在[FindByUserName 应用程序](#findbyusername-sample)中找到完整的指南。|
|---|---------------------------------------------------------------------------------------------|
[Index](../index.html)
## 假设
该指南假定你已经通过使用内置的 Redis 配置支持向应用程序添加了 Spring Session 。该指南还假定你已经对应用程序应用了 Spring 安全性。然而,我们的指南是有点通用的目的,可以应用于任何技术,只需最小的变化,这一点我们将在后面的指南中进行讨论。
| |如果需要学习如何将 Spring Session 添加到项目中,请参见[样本和指南](../#samples)列表|
|---|--------------------------------------------------------------------------------------------------------------------|
## 关于样本
我们的样例使用此功能使可能受到危害的用户会话无效。考虑以下场景:
* 用户到库并对应用程序进行身份验证。
* 用户回家后发现自己忘了注销。
* 用户可以使用位置、创建时间、最后访问时间等线索从库中登录并结束会话。
如果我们可以让用户在库中的会话从他们用来进行身份验证的任何设备上失效,这不是很好吗?这个示例演示了这是如何实现的。
## 使用`FindByIndexNameSessionRepository`
要通过用户名查找用户,你必须首先选择一个实现[`FindByIndexNameSessionRepository`]的`SessionRepository`。我们的示例应用程序假定已经设置了 Redis 支持,因此我们已经准备好了。
## 映射用户名
`FindByIndexNameSessionRepository`如果开发人员指示 Spring Session 什么用户与`Session`相关联,则只能通过用户名找到会话。你可以通过确保将名称`FindByUsernameSessionRepository.PRINCIPAL_NAME_INDEX_NAME`的会话属性填充为用户名来做到这一点。
一般来说,你可以在用户进行身份验证后立即使用以下代码来完成此操作:
```
String username = "username";
this.session.setAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username);
```
## 映射具有 Spring 安全性的用户名
由于我们使用 Spring 安全性,用户名将自动为我们建立索引。这意味着我们不需要执行任何步骤来确保对用户名进行索引。
## 向会话添加附加数据
将附加信息(如 IP 地址、浏览器、位置和其他详细信息)与会话关联起来可能会很好。这样做可以使用户更容易地知道他们正在查看的会话。
要做到这一点,请确定你想要使用的会话属性以及你想要提供的信息。然后创建一个作为会话属性添加的 Java Bean。例如,我们的示例应用程序包括会话的位置和访问类型,如下面的清单所示:
```
public class SessionDetails implements Serializable {
private String location;
private String accessType;
public String getLocation() {
return this.location;
}
public void setLocation(String location) {
this.location = location;
}
public String getAccessType() {
return this.accessType;
}
public void setAccessType(String accessType) {
this.accessType = accessType;
}
private static final long serialVersionUID = 8850489178248613501L;
}
```
然后,我们使用`SessionDetailsFilter`将该信息注入到每个 HTTP 请求的会话中,如下例所示:
```
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(request, response);
HttpSession session = request.getSession(false);
if (session != null) {
String remoteAddr = getRemoteAddress(request);
String geoLocation = getGeoLocation(remoteAddr);
SessionDetails details = new SessionDetails();
details.setAccessType(request.getHeader("User-Agent"));
details.setLocation(remoteAddr + " " + geoLocation);
session.setAttribute("SESSION_DETAILS", details);
}
}
```
我们获得我们想要的信息,然后将`SessionDetails`设置为`Session`中的一个属性。当我们通过用户名检索`Session`时,我们可以使用会话来访问我们的`SessionDetails`,就像我们将访问任何其他会话属性一样。
| |你可能想知道为什么 Spring Session 没有提供`SessionDetails`开箱即用的功能。<br/>我们有两个原因。<br/>第一个原因是应用程序自己实现这一点是非常琐碎的。<br/>第二个原因是信息会话中填充的内容(以及信息更新的频率)高度依赖于应用程序。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
## 查找特定用户的会话
现在,我们可以为特定用户找到所有会话。下面的示例展示了如何做到这一点:
```
@Autowired
FindByIndexNameSessionRepository<? extends Session> sessions;
@RequestMapping("/")
public String index(Principal principal, Model model) {
Collection<? extends Session> usersSessions = this.sessions.findByPrincipalName(principal.getName()).values();
model.addAttribute("sessions", usersSessions);
return "index";
}
```
在我们的例子中,我们找到了当前登录用户的所有会话。但是,你可以对此进行修改,以便管理员使用表单来指定要查找的用户。
## `findbyusername`示例应用程序
本节介绍如何使用`findbyusername`示例应用程序。
### 运行`findbyusername`示例应用程序
你可以通过获取[源代码](https://github.com/spring-projects/spring-session/archive/main.zip)并调用以下命令来运行示例:
```
$ ./gradlew :spring-session-sample-boot-findbyusername:bootRun
```
| |要使示例工作,你必须在 localhost 上[安装 Redis2.8+](https://redis.io/download)并使用默认端口(6379)运行它。<br/>或者,你可以更新`RedisConnectionFactory`以指向 Redis 服务器。<br/>另一个选项是使用[Docker](https://www.docker.com/)在 localhost 上运行 Redis。<br/>有关详细说明,请参见[Docker Redis 存储库](https://hub.docker.com/_/redis/)。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
现在你应该可以在[http://localhost:8080/](http://localhost:8080/)上访问应用程序了。
### 探索安全示例应用程序
你现在可以尝试使用该应用程序了。输入以下内容即可登录:
* **用户 Name ** *User *
* **密码** *密码*
现在点击**登录**按钮。你现在应该会看到一条消息,表明你是用先前输入的用户登录的。你还应该看到当前登录用户的活动会话列表。
你可以通过执行以下操作来模拟我们在[关于样本](#_about_the_sample)部分中讨论的流程:
* 打开一个新的隐身窗口并导航到[http://localhost:8080/](http://localhost:8080/)
* 输入以下内容即可登录:
* **用户 Name ** *User *
* **密码** *密码*
* 结束你的原始会话。
* 刷新原始窗口并查看你已注销。
\ No newline at end of file
# Spring Session - WebSocket
本指南描述了如何使用 Spring Session 来确保 WebSocket 消息使你的 HttpSession 保持活跃。
| |Spring session 的 WebSocket 支持仅对 Spring 的 WebSocket 支持有效。<br/>具体来说,它不能直接使用[JSR-356](https://www.jcp.org/en/jsr/detail?id=356),因为 JSR-356 没有拦截传入 WebSocket 消息的机制。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
[Index](../index.html)
## HttpSession 设置
第一步是将 Spring Session 与 HttpSession 集成在一起。这些步骤已经在[HttpSession with Redis 指南](./boot-redis.html)中进行了概述。
在继续之前,请确保你已经将 Spring Session 集成到 HttpSession 中。
## Spring 配置
在典型的 Spring WebSocket 应用程序中,你将实现`WebSocketMessageBrokerConfigurer`。例如,配置可能如下所示:
```
@Configuration
@EnableScheduling
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/messages").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/app");
}
}
```
我们可以更新配置以使用 Spring Session 的 WebSocket 支持。下面的示例展示了如何做到这一点:
SRC/main/java/samples/config/websocketconfig.java
```
@Configuration
@EnableScheduling
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractSessionWebSocketMessageBrokerConfigurer<Session> { (1)
@Override
protected void configureStompEndpoints(StompEndpointRegistry registry) { (2)
registry.addEndpoint("/messages").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/app");
}
}
```
要连接 Spring Session 支持,我们只需要更改两件事:
|**1**|而不是实现`WebSocketMessageBrokerConfigurer`,我们扩展`AbstractSessionWebSocketMessageBrokerConfigurer`|
|-----|-----------------------------------------------------------------------------------------------------------------------|
|**2**|我们将`registerStompEndpoints`方法重命名为`configureStompEndpoints`|
`AbstractSessionWebSocketMessageBrokerConfigurer`在幕后做什么?
* `WebSocketConnectHandlerDecoratorFactory`作为`WebSocketHandlerDecoratorFactory`添加到`WebSocketTransportRegistration`。这确保了一个包含`WebSocketSession`的自定义`SessionConnectEvent`被触发。当 Spring Session 结束时,要结束任何仍处于打开状态的 WebSocket 连接,`WebSocketSession`是必需的。
* `SessionRepositoryMessageInterceptor`作为`HandshakeInterceptor`添加到每个`StompWebSocketEndpointRegistration`。这确保将`Session`添加到 WebSocket 属性中,以允许更新上次访问的时间。
* `SessionRepositoryMessageInterceptor`作为`ChannelInterceptor`添加到我们的入站`ChannelRegistration`中。这确保了每次接收入站消息时,都会更新我们 Spring Session 的最后一次访问时间。
* `WebSocketRegistryListener`被创建为 Spring Bean。这确保了我们将所有`Session`ID 映射到相应的 WebSocket 连接。通过维护此映射,我们可以在 Spring Session 结束时关闭所有 WebSocket 连接。
## `websocket`示例应用程序
`websocket`示例应用程序演示了如何在 WebSockets 中使用 Spring Session 。
### 运行`websocket`示例应用程序
你可以通过获取[源代码](https://github.com/spring-projects/spring-session/archive/main.zip)并调用以下命令来运行示例:
```
$ ./gradlew :spring-session-sample-boot-websocket:bootRun
```
| |为了测试会话过期,你可能希望在启动应用程序之前添加以下配置属性,从而将会话过期时间更改为 1 分钟(默认为 30 分钟):<br/><br/>SRC/main/resources/application.properties<br/><br/>```<br/>server.servlet.session.timeout=1m # Session timeout. If a duration suffix is not specified, seconds will be used.<br/>```|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| |要使示例工作,你必须在 localhost 上[安装 Redis2.8+](https://redis.io/download)并使用默认端口(6379)运行它。<br/>或者,你可以更新`RedisConnectionFactory`以指向 Redis 服务器。<br/>另一个选项是使用[Docker](https://www.docker.com/)在 localhost 上运行 Redis。<br/>有关详细说明,请参见[Docker Redis 存储库](https://hub.docker.com/_/redis/)。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
现在你应该可以在[http://localhost:8080/](http://localhost:8080/)上访问应用程序了。
### 探索`websocket`示例应用程序
现在你可以尝试使用该应用程序了。使用以下信息进行身份验证:
* **用户 Name ** *罗布*
* **密码** *密码*
现在点击**登录**按钮。你现在应该被验证为用户**罗布**
打开一个隐身窗口并访问[http://localhost:8080/](http://localhost:8080/)
系统会提示你输入登录表单。使用以下信息进行身份验证:
* **用户 Name ** *卢克*
* **密码** *密码*
现在把罗布的话传给卢克。消息应该会出现。
等两分钟,再试着把罗布的信息发送给卢克。你可以看到该消息已不再发送。
| |为什么是两分钟?<br/><br/> Spring Session 在 60 秒内到期,但是来自 Redis 的通知不能保证在 60 秒内发生。<br/>以确保套接字在合理的时间内关闭, Spring Session 在 00 秒时每分钟运行一个后台任务,该任务强制清除任何过期的会话。<br/>这意味着在关闭 WebSocket 连接之前,你最多需要等待两分钟。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
你现在可以尝试访问[http://localhost:8080/](http://localhost:8080/),提示你再次进行身份验证。这表明会话正确地过期了。
现在重复同样的练习,但不是等待两分钟,而是每 30 秒发送一条来自每个用户的消息。你可以看到消息继续被发送。尝试访问[http://localhost:8080/](http://localhost:8080/),没有提示你再次进行身份验证。这表明会话是保持活力的。
| |只有来自用户的消息才能使会话保持活跃。<br/>这是因为只有来自用户的消息才意味着用户活动。<br/>收到的消息并不意味着活动,因此不会更新会话过期时间。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
\ No newline at end of file
# `HttpSession`积分
Spring Session 提供了与`HttpSession`的透明集成。这意味着开发人员可以使用 Spring Session 支持的实现来切换`HttpSession`实现。
## 为什么 Spring session 和`HttpSession`?
我们已经提到, Spring Session 提供了与`HttpSession`的透明集成,但是我们从中得到了什么好处呢?
* **群集会话**: Spring Session 使得在不绑定到特定于应用程序容器的解决方案的情况下支持[群集会话](#httpsession-redis)变得非常简单。
* **RESTful API**: Spring session 让在 header 中提供会话 ID 可以与[RESTful API](#httpsession-rest)一起工作
## `HttpSession`with redis
使用 Spring session with`HttpSession`是通过在使用`HttpSession`之前添加一个 Servlet 过滤器来实现的。你可以通过以下两种方式选择启用此功能:
* [基于 Java 的配置](#httpsession-redis-jc)
* [基于 XML 的配置](#httpsession-redis-xml)
### Redis 基于 Java 的配置
这一节描述了如何使用基于 Java 的配置使用 Redis 来支持`HttpSession`
| |[HttpSession 示例](samples.html#samples)提供了如何使用 Java 配置集成 Spring Session 和`HttpSession`的工作示例,<br/>你可以在接下来的几节中阅读集成的基本步骤,但是,我们鼓励你在与自己的应用程序集成时遵循详细的 HttpSession 指南。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### Spring Java 配置
在添加了所需的依赖关系之后,我们就可以创建我们的 Spring 配置了。 Spring 配置负责创建一个 Servlet 过滤器,该过滤器将`HttpSession`实现替换为由 Spring Session 支持的实现。为此,添加以下 Spring 配置:
```
@EnableRedisHttpSession (1)
public class Config {
@Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory(); (2)
}
}
```
|**1**|`@EnableRedisHttpSession`注释创建了一个名为`springSessionRepositoryFilter`的 Spring Bean,它实现了`Filter`<br/>过滤器负责替换要由 Spring Session 支持的`HttpSession`实现。<br/>在此实例中, Spring Session 由 Redis 支持。|
|-----|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|我们创建一个`RedisConnectionFactory`将 Spring Session 连接到 Redis 服务器。<br/>我们将连接配置为在默认端口(6379)上连接到 localhost。<br/>有关配置 Spring 数据 Redis 的更多信息,请参见[参考文献](https://docs.spring.io/spring-data/data-redis/docs/2.6.2/reference/html/)。|
#### Java Servlet 容器初始化
我们的[Spring Configuration](#httpsession-spring-configuration)创建了一个名为`springSessionRepositoryFilter`的 Spring Bean,它实现了`Filter``springSessionRepositoryFilter` Bean 负责用 Spring Session 支持的自定义实现替换`HttpSession`
为了让我们的`Filter`发挥其魔力, Spring 需要加载我们的`Config`类。最后,我们需要确保我们的 Servlet 容器(即 Tomcat)为每个请求使用我们的`springSessionRepositoryFilter`。幸运的是, Spring Session 提供了一个名为`AbstractHttpSessionApplicationInitializer`的实用程序类,以使这两个步骤都变得容易。以下是一个例子:
SRC/main/java/sample/initializer.java
```
public class Initializer extends AbstractHttpSessionApplicationInitializer { (1)
public Initializer() {
super(Config.class); (2)
}
}
```
| |我们类的名称(`Initializer`)并不重要。重要的是,我们扩展`AbstractHttpSessionApplicationInitializer`。|
|---|---------------------------------------------------------------------------------------------------------------------------------------|
|**1**|第一步是扩展`AbstractHttpSessionApplicationInitializer`<br/>这样做可以确保 Spring Bean 以`springSessionRepositoryFilter`的名称在我们的 Servlet 容器中为每个请求注册。|
|-----|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|`AbstractHttpSessionApplicationInitializer`还提供了一种机制,以确保 Spring 加载我们的`Config`。|
### 基于 XML 的 Redis 配置
本节介绍如何使用基于 XML 的配置使用 Redis 来支持`HttpSession`
| |[HttpSession XML 示例](samples.html#samples)提供了如何使用 XML 配置集成 Spring Session 和`HttpSession`的工作示例,<br/>你可以在接下来的几个部分中阅读集成的基本步骤,但是,我们鼓励你在与自己的应用程序集成时遵循详细的 HttpSession XML 指南。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### Spring xml 配置
在添加了所需的依赖关系之后,我们就可以创建我们的 Spring 配置了。 Spring 配置负责创建一个 Servlet 过滤器,该过滤器将`HttpSession`实现替换为由 Spring Session 支持的实现。为此,添加以下 Spring 配置:
SRC/main/webapp/WEB-INF/ Spring/session.xml
```
(1)
<context:annotation-config/>
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>
(2)
<bean class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory"/>
```
|**1**|我们使用`<context:annotation-config/>``RedisHttpSessionConfiguration`的组合,因为 Spring Session 尚未提供 XML 名称空间支持(参见[gh-104](https://github.com/spring-projects/spring-session/issues/104))。<br/>这将创建一个名称为`springSessionRepositoryFilter`的 Spring Bean 实现`Filter`<br/>过滤器负责替换要由 Spring Session 支持的`HttpSession`实现。<br/>在此实例中, Spring Session 由 Redis 支持。|
|-----|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|我们创建一个`RedisConnectionFactory`将 Spring Session 连接到 Redis 服务器。<br/>我们将连接配置为在默认端口(6379)<br/>上连接到 localhost。有关配置 Spring 数据 Redis 的更多信息,请参见[参考文献](https://docs.spring.io/spring-data/data-redis/docs/2.6.2/reference/html/)。|
#### xml Servlet 容器初始化
我们的[Spring Configuration](#httpsession-xml-spring-configuration)创建了一个名为`springSessionRepositoryFilter`的 Spring Bean,它实现了`Filter``springSessionRepositoryFilter` Bean 负责用 Spring Session 支持的自定义实现替换`HttpSession`
为了使我们的`Filter`发挥其魔力,我们需要指示 Spring 加载我们的`session.xml`配置。我们可以通过以下配置来实现这一点:
SRC/main/webapp/WEB-INF/web.xml
```
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/session.xml
</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
```
[`ContextLoaderListener`](https://DOCS. Spring.io/ Spring/DOCS/5.3.16/ Spring-framework-reference/core.html#context-create)读取 contextconfiglocation 并获取 session.xml 配置。
最后,我们需要确保我们的 Servlet 容器(即 Tomcat)为每个请求使用我们的`springSessionRepositoryFilter`。下面的代码片段为我们执行了最后一步:
SRC/main/webapp/WEB-INF/web.xml
```
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
```
[`DelegatingFilterProxy`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/web/filter/delegatingfilterproxy.html)以`springSessionRepositoryFilter`的名称查找 Bean 并将其强制转换为`Filter`。对于每一个调用`DelegatingFilterProxy`的请求,都会调用`springSessionRepositoryFilter`
### HttpSession with Mongo
使用 Spring 与`HttpSession`会话是通过在使用`HttpSession`的任何之前添加一个 Servlet 过滤器来实现的。
本节介绍如何使用基于 Java 的配置使用 Mongo 返回`HttpSession`
| |[HttpSession Mongo 示例](#samples)提供了一个工作示例,说明如何使用 Java 配置集成 Spring Session 和`HttpSession`<br/>你可以阅读下面的集成基本步骤,但鼓励你在与自己的应用程序集成时遵循详细的 HttpSession 指南。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
你所要做的就是添加以下 Spring 配置:
```
@EnableMongoHttpSession (1)
public class HttpSessionConfig {
@Bean
public JdkMongoSessionConverter jdkMongoSessionConverter() {
return new JdkMongoSessionConverter(Duration.ofMinutes(30)); (2)
}
}
```
|**1**|`@EnableMongoHttpSession`注释创建了一个名为`springSessionRepositoryFilter`的 Spring Bean,它实现了 filter。<br/>这个过滤器用 MongoDB 支持的 Bean 替换了默认的`HttpSession`。|
|-----|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|将会话超时时间配置为 30 分钟。|
#### 会话序列化机制
为了能够在 MongoDB 中持久化会话对象,我们需要提供序列化/反序列化机制。
默认情况下, Spring Session MongoDB 将使用`JdkMongoSessionConverter`
但是,只需在启动应用程序中添加以下代码,就可以切换到`JacksonMongoSessionConverter`:
```
@Bean
JacksonMongoSessionConverter mongoSessionConverter() {
return new JacksonMongoSessionConverter();
}
```
##### JacksonMongosessionConverter
这种机制使用 Jackson 序列化到/来自 JSON 的会话对象。
通过创建以下内容 Bean:
```
@Bean
JacksonMongoSessionConverter mongoSessionConverter() {
return new JacksonMongoSessionConverter();
}
```
…你可以从默认的(基于 JDK 的序列化)切换到使用 Jackson。
| |如果你正在与 Spring Security 集成(通过将你的会话存储在 MongoDB 中),则该配置将<br/>注册适当的白名单组件,以便 Spring Security 能够正常工作。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
如果你想提供自定义的 Jackson 模块,你可以通过显式地注册如下所示的模块来实现这一点:
```
@Configuration
@EnableMongoHttpSession
static class Config extends BaseConfig {
@Bean
AbstractMongoSessionConverter mongoSessionConverter() {
return new JacksonMongoSessionConverter(Collections.singletonList(new GeoModule()));
}
}
```
##### JDKMongosessionConverter
`JdkMongoSessionConverter`使用标准的 Java 序列化来持久化以二进制形式映射到 MongoDB 的会话属性。但是,诸如 ID、访问时间等标准会话元素仍然被写成一个普通的 Mongo 对象,并且可以在不需要额外的努力的情况下进行读取和查询。如果没有明确的`JdkMongoSessionConverter` Bean 定义,则使用`AbstractMongoSessionConverter`
还有一个构造函数接受`Serializer``Deserializer`对象,允许你传递自定义的实现,这在你想要使用非默认的类加载器时尤其重要。
## `HttpSession`with JDBC
通过在使用`HttpSession`的任何内容之前添加一个 Servlet 过滤器,可以使用与`HttpSession`的 Spring Session 。你可以选择以下任何一种方式:
* [基于 Java 的配置](#httpsession-jdbc-jc)
* [基于 XML 的配置](#httpsession-jdbc-xml)
* [Spring Boot-based Configuration](#httpsession-jdbc-boot)
### 基于 Java 的 JDBC 配置
本节描述了在使用基于 Java 的配置时如何使用关系数据库来备份`HttpSession`
| |[HttpSession JDBC 示例](samples.html#samples)提供了如何通过使用 Java 配置来集成 Spring Session 和`HttpSession`的工作示例,<br/>你可以在接下来的几个部分中阅读集成的基本步骤,但我们鼓励你在与自己的应用程序集成时遵循详细的 HttpSession JDBC 指南。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### Spring Java 配置
在添加了所需的依赖关系之后,我们就可以创建我们的 Spring 配置了。 Spring 配置负责创建一个 Servlet 过滤器,该过滤器用 Spring Session 支持的实现替换`HttpSession`实现。为此,添加以下 Spring 配置:
```
@EnableJdbcHttpSession (1)
public class Config {
@Bean
public EmbeddedDatabase dataSource() {
return new EmbeddedDatabaseBuilder() (2)
.setType(EmbeddedDatabaseType.H2).addScript("org/springframework/session/jdbc/schema-h2.sql").build();
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource); (3)
}
}
```
|**1**|`@EnableJdbcHttpSession`注释创建了一个名为`springSessionRepositoryFilter`的 Spring Bean。<br/>该 Bean 实现了`Filter`<br/>过滤器负责替换要由 Spring Session 支持的`HttpSession`实现。<br/>在此实例中, Spring Session 是由关系数据库支持的。|
|-----|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|我们创建了一个`dataSource`,它将 Spring Session 连接到 H2 数据库的嵌入式实例。<br/>我们通过使用 Spring Session 中包含的 SQL 脚本来配置 H2 数据库来创建数据库表。|
|**3**|我们创建一个`transactionManager`,它管理先前配置的`dataSource`的事务。|
有关如何配置与数据访问相关的问题的更多信息,请参见[Spring Framework Reference Documentation](https://docs.spring.io/spring/docs/5.3.16/spring-framework-reference/data-access.html)
#### Java Servlet 容器初始化
我们的[Spring Configuration](#httpsession-jdbc-spring-configuration)创建了一个名为`springSessionRepositoryFilter`的 Spring Bean,它实现了`Filter``springSessionRepositoryFilter` Bean 负责用 Spring Session 支持的自定义实现替换`HttpSession`
为了让我们的`Filter`发挥其魔力, Spring 需要加载我们的`Config`类。最后,我们需要确保我们的 Servlet 容器(即 Tomcat)为每个请求使用我们的`springSessionRepositoryFilter`。幸运的是, Spring Session 提供了一个名为`AbstractHttpSessionApplicationInitializer`的实用程序类,以使这两个步骤都变得容易。下面的示例展示了如何做到这一点:
SRC/main/java/sample/initializer.java
```
public class Initializer extends AbstractHttpSessionApplicationInitializer { (1)
public Initializer() {
super(Config.class); (2)
}
}
```
| |类的名称(初始化器)并不重要。<br/>重要的是我们扩展`AbstractHttpSessionApplicationInitializer`。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------|
|**1**|第一步是扩展`AbstractHttpSessionApplicationInitializer`<br/>这样做可以确保名为`springSessionRepositoryFilter`的 Spring Bean 在我们的 Servlet 容器中为每个请求注册。|
|-----|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|`AbstractHttpSessionApplicationInitializer`还提供了一种机制,以确保 Spring 加载我们的`Config`。|
#### 多个数据源
Spring Session 提供了`@SpringSessionDataSource`限定符,允许你显式地声明应该在`JdbcIndexedSessionRepository`中注入哪个`DataSource` Bean。这在应用程序上下文中存在多个`DataSource`bean 的场景中特别有用。
下面的示例展示了如何做到这一点:
config.java
```
@EnableJdbcHttpSession
public class Config {
@Bean
@SpringSessionDataSource (1)
public EmbeddedDatabase firstDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2).addScript("org/springframework/session/jdbc/schema-h2.sql").build();
}
@Bean
public HikariDataSource secondDataSource() {
// ...
}
}
```
|**1**|此限定符声明 Spring Session 将使用 FirstDataSource。|
|-----|-----------------------------------------------------------------------------|
### 基于 XML 的 JDBC 配置
本节描述了在使用基于 XML 的配置时如何使用关系数据库来备份`HttpSession`
| |[HttpSession JDBC XML 示例](samples.html#samples)提供了如何通过使用 XML 配置来集成 Spring Session 和`HttpSession`的工作示例,<br/>你可以在接下来的几个部分中阅读集成的基本步骤,但是,我们鼓励你在与自己的应用程序集成时遵循详细的 HttpSession JDBC XML 指南。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### Spring xml 配置
在添加了所需的依赖关系之后,我们可以创建我们的 Spring 配置。 Spring 配置负责创建一个 Servlet 过滤器,该过滤器用 Spring Session 支持的实现替换`HttpSession`实现。下面的清单显示了如何添加以下 Spring 配置:
SRC/main/webapp/WEB-INF/ Spring/session.xml
```
(1)
<context:annotation-config/>
<bean class="org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessionConfiguration"/>
(2)
<jdbc:embedded-database id="dataSource" database-name="testdb" type="H2">
<jdbc:script location="classpath:org/springframework/session/jdbc/schema-h2.sql"/>
</jdbc:embedded-database>
(3)
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource"/>
</bean>
```
|**1**|我们使用`<context:annotation-config/>`的组合和`JdbcHttpSessionConfiguration`因为 Spring session 尚未提供 XML 名称空间支持(参见[gh-104](https://github.com/spring-projects/spring-session/issues/104))。<br/>这将创建一个名为`springSessionRepositoryFilter`的 Spring Bean。<br/> Bean 实现`Filter`<br/>过滤器负责替换要由 Spring session 支持的`HttpSession`实现。<br/>在此实例中, Spring Session 由关系数据库支持。|
|-----|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|我们创建一个`dataSource`,它将 Spring Session 连接到 H2 数据库的嵌入式实例。<br/>我们通过使用 Spring Session 中包含的 SQL 脚本配置 H2 数据库来创建数据库表。|
|**3**|我们创建一个`transactionManager`,它管理先前配置的`dataSource`的事务。|
有关如何配置与数据访问相关的问题的更多信息,请参见[Spring Framework Reference Documentation](https://docs.spring.io/spring/docs/5.3.16/spring-framework-reference/data-access.html)
#### xml Servlet 容器初始化
我们的[Spring Configuration](#httpsession-jdbc-xml-spring-configuration)创建了一个名为`springSessionRepositoryFilter`的 Spring Bean,它实现了`Filter``springSessionRepositoryFilter` Bean 负责用 Spring Session 支持的自定义实现替换`HttpSession`
为了使我们的`Filter`发挥其魔力,我们需要指示 Spring 加载我们的`session.xml`配置。我们的配置如下:
SRC/main/webapp/WEB-INF/web.xml
```
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/session.xml
</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
```
[`ContextLoaderListener`](https://DOCS. Spring.io/ Spring/DOCS/5.3.16/ Spring-framework-reference/core.html#context-create)读取`contextConfigLocation`并获取 session.xml 配置。
最后,我们需要确保我们的 Servlet 容器(即 Tomcat)为每个请求使用我们的`springSessionRepositoryFilter`。下面的代码片段为我们执行了最后一步:
SRC/main/webapp/WEB-INF/web.xml
```
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
```
[`DelegatingFilterProxy`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/web/filter/delegatingfilterproxy.html)查找一个名为`springSessionRepositoryFilter`的 Bean 并将其强制转换为`Filter`。对于每个调用`DelegatingFilterProxy`的请求,都调用`springSessionRepositoryFilter`
### JDBC Spring 基于引导的配置
本节描述了在使用 Spring 引导时如何使用关系数据库来备份`HttpSession`
| |[ HttpSession JDBC Spring Boot Sample](samples.html#samples)提供了如何使用 Spring boot 集成 Spring Session 和`HttpSession`的工作示例,<br/>你可以在接下来的几个部分中阅读集成的基本步骤,但是,我们鼓励你在与自己的应用程序集成时遵循详细的 HttpSession JDBC Spring 引导指南。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### Spring 引导配置
在添加了所需的依赖项之后,我们可以创建我们的 Spring 引导配置。多亏了一流的自动配置支持,在关系数据库支持下设置 Spring Session 就像向`application.properties`添加一个配置属性一样简单。下面的清单展示了如何做到这一点:
SRC/主/资源/应用程序.properties
```
spring.session.store-type=jdbc # Session store type.
```
如果在 Classpath 上存在单个 Spring Session 模块,则 Spring 引导自动使用该存储实现。如果有多个实现,则必须选择要用来存储会话的 StoreType,如上面所示。
Spring Boot 应用的配置相当于手动添加`@EnableJdbcHttpSession`注释。这将创建一个名为`springSessionRepositoryFilter`的 Spring Bean。 Bean 实现`Filter`。过滤器负责替换要由 Spring Session 支持的`HttpSession`实现。
你可以使用`application.properties`来进一步自定义。下面的清单展示了如何做到这一点:
SRC/主/资源/应用程序.properties
```
server.servlet.session.timeout= # Session timeout. If a duration suffix is not specified, seconds are used.
spring.session.jdbc.initialize-schema=embedded # Database schema initialization mode.
spring.session.jdbc.schema=classpath:org/springframework/session/jdbc/[email protected]@[email protected]@.sql # Path to the SQL file to use to initialize the database schema.
spring.session.jdbc.table-name=SPRING_SESSION # Name of the database table used to store sessions.
```
有关更多信息,请参见 Spring 引导文档的[Spring Session](https://docs.spring.io/spring-boot/docs/2.5.6/reference/htmlsingle/#boot-features-session)部分。
#### 配置`DataSource`
Spring 启动会自动创建`DataSource`,该会话将 Spring Session 连接到 H2 数据库的嵌入式实例。在生产环境中,你需要更新配置以指向关系数据库。例如,你可以在应用程序中包含以下内容:
SRC/主/资源/应用程序.properties
```
spring.datasource.url= # JDBC URL of the database.
spring.datasource.username= # Login username of the database.
spring.datasource.password= # Login password of the database.
```
有关更多信息,请参见 Spring 引导文档的[配置数据源](https://docs.spring.io/spring-boot/docs/2.5.6/reference/htmlsingle/#boot-features-configure-datasource)部分。
#### Servlet 容器初始化
我们的[Spring Boot Configuration](#httpsession-jdbc-boot-spring-configuration)创建了一个名为`springSessionRepositoryFilter`的 Spring Bean,它实现了`Filter``springSessionRepositoryFilter` Bean 负责用 Spring Session 支持的自定义实现替换`HttpSession`
为了让我们的`Filter`发挥其魔力, Spring 需要加载我们的`Config`类。最后,我们需要确保我们的 Servlet 容器(即 Tomcat)为每个请求使用我们的`springSessionRepositoryFilter`。幸运的是,Boot 为我们解决了这两个步骤。
## HttpSession with Hazelcast
使用 Spring 与`HttpSession`会话是通过在任何使用`HttpSession`的之前添加一个 Servlet 过滤器来实现的。
本节介绍如何使用基于 Java 的配置使用 HazelCast 来支持`HttpSession`
| |[ Hazelcast Spring Sample](samples.html#samples)提供了如何通过使用 Java 配置来集成 Spring Session 和`HttpSession`的工作示例,<br/>你可以在接下来的几个部分中阅读集成的基本步骤,但是,我们鼓励你在与自己的应用程序集成时遵循详细的 Hazelcast Spring 指南。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
### Spring 配置
在添加了所需的依赖关系之后,我们可以创建我们的 Spring 配置。 Spring 配置负责创建 Servlet 过滤器,该过滤器用 Spring Session 支持的实现替换`HttpSession`实现。为此,添加以下 Spring 配置:
```
@EnableHazelcastHttpSession (1)
@Configuration
public class HazelcastHttpSessionConfig {
@Bean
public HazelcastInstance hazelcastInstance() {
Config config = new Config();
MapAttributeConfig attributeConfig = new MapAttributeConfig()
.setName(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
.setExtractor(PrincipalNameExtractor.class.getName());
config.getMapConfig(HazelcastIndexedSessionRepository.DEFAULT_SESSION_MAP_NAME) (2)
.addMapAttributeConfig(attributeConfig).addMapIndexConfig(
new MapIndexConfig(HazelcastIndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE, false));
SerializerConfig serializerConfig = new SerializerConfig();
serializerConfig.setImplementation(new HazelcastSessionSerializer()).setTypeClass(MapSession.class);
config.getSerializationConfig().addSerializerConfig(serializerConfig); (3)
return Hazelcast.newHazelcastInstance(config); (4)
}
}
```
|**1**|`@EnableHazelcastHttpSession`注释创建了一个名为`springSessionRepositoryFilter`的 Spring Bean,它实现了`Filter`<br/>过滤器负责替换要由 Spring Session 支持的`HttpSession`实现。<br/>在此实例中, Spring Session 由 Hazelcast 支持。|
|-----|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|为了支持通过主名索引检索会话,需要注册一个适当的`ValueExtractor`<br/> Spring Session 为此提供了`PrincipalNameExtractor`。|
|**3**|为了有效地序列化`MapSession`对象,需要注册`HazelcastSessionSerializer`。如果未设置<br/>,HazelCast 将使用本机 Java 序列化来序列化会话。|
|**4**|我们创建一个`HazelcastInstance`将 Spring Session 连接到 Hazelcast。<br/>默认情况下,应用程序启动并连接到 Hazelcast 的嵌入式实例。<br/>有关配置 Hazelcast 的更多信息,请参见[参考文献](https://docs.hazelcast.org/docs/3.12.12/manual/html-single/index.html#hazelcast-configuration)。|
| |如果`HazelcastSessionSerializer`是首选的,则需要在所有 Hazelcast 集群成员启动之前对其进行配置。<br/>在 Hazelcast 集群中,所有成员都应该对会话使用相同的序列化方法。此外,如果使用了 HazelCast 客户机/服务器拓扑<br/>,那么成员和客户机都必须使用相同的序列化方法。序列化器可以通过`ClientConfig``SerializerConfiguration`相同的成员来注册。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
### Servlet 容器初始化
我们的[Spring Configuration](guides/java-security.html#security-spring-configuration)创建了一个名为`springSessionRepositoryFilter`的 Spring Bean,它实现了`Filter``springSessionRepositoryFilter` Bean 负责用 Spring Session 支持的自定义实现替换`HttpSession`
为了让我们的`Filter`发挥其魔力, Spring 需要加载我们的`SessionConfig`类。由于我们的应用程序已经通过使用`SecurityInitializer`类加载了 Spring 配置,因此我们可以将`SessionConfig`类添加到其中。下面的清单展示了如何做到这一点:
SRC/main/java/sample/securityinitializer.java
```
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
public SecurityInitializer() {
super(SecurityConfig.class, SessionConfig.class);
}
}
```
最后,我们需要确保我们的 Servlet 容器(即 Tomcat)为每个请求使用我们的`springSessionRepositoryFilter`。在 Spring security 的`springSecurityFilterChain`之前调用 Spring session 的`springSessionRepositoryFilter`是极其重要的。这样做可以确保 Spring 安全性使用的`HttpSession`得到 Spring Session 的支持。幸运的是, Spring Session 提供了一个名为`AbstractHttpSessionApplicationInitializer`的实用程序类,这使得这样做很容易。下面的示例展示了如何做到这一点:
SRC/main/java/sample/initializer.java
```
public class Initializer extends AbstractHttpSessionApplicationInitializer {
}
```
| |我们类的名称(`Initializer`)并不重要。重要的是,我们扩展`AbstractHttpSessionApplicationInitializer`。|
|---|---------------------------------------------------------------------------------------------------------------------------------------|
通过扩展`AbstractHttpSessionApplicationInitializer`,我们确保在 Spring Security 的`springSecurityFilterChain`之前的每个请求都将名为`springSessionRepositoryFilter`的 Spring Bean 注册到我们的 Servlet 容器中。
## 集成如何工作
幸运的是,`HttpSession``HttpServletRequest`(获取`HttpSession`的 API)都是接口。这意味着我们可以为这些 API 中的每一个提供我们自己的实现。
| |本节描述 Spring Session 如何提供与`HttpSession`的透明集成。我们提供这些内容,这样你就可以了解幕后发生了什么。这个功能已经集成了,你不需要自己实现这个逻辑。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
首先,我们创建一个自定义的`HttpServletRequest`,它返回一个`HttpSession`的自定义实现。它看起来是这样的:
```
public class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {
public SessionRepositoryRequestWrapper(HttpServletRequest original) {
super(original);
}
public HttpSession getSession() {
return getSession(true);
}
public HttpSession getSession(boolean createNew) {
// create an HttpSession implementation from Spring Session
}
// ... other methods delegate to the original HttpServletRequest ...
}
```
返回`HttpSession`的任何方法都将被重写。所有其他方法都是通过`HttpServletRequestWrapper`实现的,并委托给原始的`HttpServletRequest`实现。
我们使用一个名为`SessionRepositoryFilter`的 Servlet `Filter`替换`HttpServletRequest`实现。下面的伪代码展示了它是如何工作的:
```
public class SessionRepositoryFilter implements Filter {
public doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
SessionRepositoryRequestWrapper customRequest =
new SessionRepositoryRequestWrapper(httpRequest);
chain.doFilter(customRequest, response, chain);
}
// ...
}
```
通过将自定义`HttpServletRequest`实现传递到`FilterChain`中,我们确保在`Filter`之后调用的任何内容都使用自定义`HttpSession`实现。这突出了为什么将 Spring Session 的`SessionRepositoryFilter`放在与`HttpSession`交互的任何事物之前是很重要的。
## `HttpSession`和 RESTful API
Spring Session 可以通过让会话在头文件中提供而与 RESTful API 一起工作。
| |[剩余样本](samples.html#samples)提供了一个工作示例,说明如何在 REST 应用程序中使用 Spring Session 来支持使用头部进行身份验证,<br/>你可以遵循下面几节中描述的集成的基本步骤,但是,我们鼓励你在与自己的应用程序集成时遵循详细的 REST 指南。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
### Spring 配置
在添加了所需的依赖关系之后,我们可以创建我们的 Spring 配置。 Spring 配置负责创建一个 Servlet 过滤器,该过滤器将`HttpSession`实现替换为由 Spring Session 支持的实现。为此,添加以下 Spring 配置:
```
@Configuration
@EnableRedisHttpSession (1)
public class HttpSessionConfig {
@Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory(); (2)
}
@Bean
public HttpSessionIdResolver httpSessionIdResolver() {
return HeaderHttpSessionIdResolver.xAuthToken(); (3)
}
}
```
|**1**|`@EnableRedisHttpSession`注释创建了一个名为`springSessionRepositoryFilter`的 Spring Bean,它实现了`Filter`<br/>过滤器负责替换要由 Spring Session 支持的`HttpSession`实现。<br/>在此实例中, Spring Session 由 Redis 支持。|
|-----|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|我们创建一个`RedisConnectionFactory`将 Spring Session 连接到 Redis 服务器。<br/>我们将连接配置为在默认端口(6379)上连接到 localhost。<br/>有关配置 Spring 数据 Redis 的更多信息,请参见[参考文献](https://docs.spring.io/spring-data/data-redis/docs/2.6.2/reference/html/)。|
|**3**|我们定制了 Spring Session 的 HttpSession 集成,以使用 HTTP 头来传递当前会话信息,而不是 Cookie。|
### Servlet 容器初始化
我们的[Spring Configuration](#rest-spring-configuration)创建了一个名为`springSessionRepositoryFilter`的 Spring Bean,它实现了`Filter``springSessionRepositoryFilter` Bean 负责用 Spring Session 支持的自定义实现替换`HttpSession`
为了使我们的`Filter`发挥其魔力, Spring 需要加载我们的`Config`类。我们在我们的 Spring `MvcInitializer`中提供了配置,如下例所示:
SRC/main/java/sample/mvc/mvcinitializer.java
```
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { SecurityConfig.class, HttpSessionConfig.class };
}
```
最后,我们需要确保我们的 Servlet 容器(即 Tomcat)为每个请求使用我们的`springSessionRepositoryFilter`。幸运的是, Spring Session 提供了一个名为`AbstractHttpSessionApplicationInitializer`的实用程序类,这使得这样做很容易。为此,使用默认构造函数扩展类,如下例所示:
SRC/main/java/sample/initializer.java
```
public class Initializer extends AbstractHttpSessionApplicationInitializer {
}
```
| |我们类的名称(`Initializer`)并不重要。重要的是,我们扩展`AbstractHttpSessionApplicationInitializer`。|
|---|---------------------------------------------------------------------------------------------------------------------------------------|
## 使用`HttpSessionListener`
Spring 通过声明`SessionEventHttpSessionListenerAdapter``SessionDestroyedEvent``SessionCreatedEvent`转换为`HttpSessionEvent`,会话支持`HttpSessionListener`。要使用此支持,你需要:
* 确保你的`SessionRepository`实现支持并配置为发射`SessionDestroyedEvent``SessionCreatedEvent`
*`SessionEventHttpSessionListenerAdapter`配置为 Spring Bean。
* 将每个`HttpSessionListener`注入`SessionEventHttpSessionListenerAdapter`
如果你使用[`HttpSession`with Redis](#HttpSession-Redis)中记录的配置支持,那么你所需要做的就是将每个`HttpSessionListener`注册为 Bean。例如,假设你希望支持 Spring Security 的并发控制,并且需要使用`HttpSessionEventPublisher`。在这种情况下,可以将`HttpSessionEventPublisher`添加为 Bean。在 Java 配置中,这可能如下所示:
```
@Configuration
@EnableRedisHttpSession
public class RedisHttpSessionConfig {
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
// ...
}
```
在 XML 配置中,这可能如下所示:
```
<bean class="org.springframework.security.web.session.HttpSessionEventPublisher"/>
```
\ No newline at end of file
# Spring Session 模块
在 Spring session1.x 中, Spring session 的所有`SessionRepository`实现都在`spring-session`工件中可用。虽然方便,但这种方法不能长期持续,因为项目中增加了更多的特性和`SessionRepository`实现。
在 Spring Session2.0 中,将几个模块拆分成独立的模块和托管存储库。 Spring MongoDB 的会话已退役,但后来作为一个单独的模块重新激活。从 Spring Session 2.6 开始,MongoDB 的 Spring Session 被合并回 Spring Session 。
现在,各种存储库和模块的情况如下:
* [`spring-session`Repository](https://github.com/ Spring-projects/ Spring-session)
* 主持 Spring Session 核心、 Spring MongoDB 会话、 Spring Redis 会话、 Spring Session JDBC 和 Spring Session Hazelcast 模块。
* [`spring-session-data-geode`Repository](https://github.com/ Spring-projects/ Spring-session-data-geode)
* 托管 Spring Session 数据 Geode 模块。 Spring Session 数据 Geode 有其自己的用户指南,你可以在[[https://spring.io/projects/spring-session-data-geode#learn](https://spring.io/projects/spring-session-data-geode#learn)站点]上找到它。
最后, Spring Session 还提供了一个 Maven BOM(“物料清单”)模块,以帮助用户处理版本管理问题:
* [`spring-session-bom`Repository](https://github.com/ Spring-projects/ Spring-session-bom)
* 主持 Spring Session BOM 模块
\ No newline at end of file
# 示例和指南(从这里开始)
要开始使用 Spring Session ,最好的起点是我们的示例应用程序。
|来源| Description | Guide |
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|
|[HttpSession with Redis](https://github.com/spring-projects/spring-session/tree/main/spring-session-samples/spring-session-sample-boot-redis)| Demonstrates how to use Spring Session to replace the `HttpSession` with Redis. | [HttpSession with Redis Guide](guides/boot-redis.html) |
|[HttpSession with JDBC](https://github.com/spring-projects/spring-session/tree/main/spring-session-samples/spring-session-sample-boot-jdbc)| Demonstrates how to use Spring Session to replace the `HttpSession` with a relational database store. | [HttpSession with JDBC Guide](guides/boot-jdbc.html) |
|[Hazelcast 的 HttpSession](https://github.com/spring-projects/spring-session/tree/main/spring-session-samples/spring-session-sample-boot-hazelcast)| Demonstrates how to use Spring Session to replace the `HttpSession` with Hazelcast. | |
|[按用户名查找 Name](https://github.com/spring-projects/spring-session/tree/main/spring-session-samples/spring-session-sample-boot-findbyusername)| Demonstrates how to use Spring Session to find sessions by username. | [Find by Username Guide](guides/boot-findbyusername.html) |
|[WebSockets](https://github.com/spring-projects/spring-session/tree/main/spring-session-samples/spring-session-sample-boot-websocket)| Demonstrates how to use Spring Session with WebSockets. | [WebSockets Guide](guides/boot-websocket.html) |
|[WebFlux](https://github.com/spring-projects/spring-session/tree/main/spring-session-samples/spring-session-sample-boot-webflux)| Demonstrates how to use Spring Session to replace the Spring WebFlux’s `WebSession` with Redis. | |
|[带有自定义 cookie 的 WebFlux](https://github.com/spring-projects/spring-session/tree/main/spring-session-samples/spring-session-sample-boot-webflux-custom-cookie)| Demonstrates how to use Spring Session to customize the Session cookie in a WebFlux based application. |[WebFlux with Custom Cookie Guide](guides/boot-webflux-custom-cookie.html)|
|[带有 Redis JSON 序列化的 HttpSession](https://github.com/spring-projects/spring-session/tree/main/spring-session-samples/spring-session-sample-boot-redis-json)| Demonstrates how to use Spring Session to replace the `HttpSession` with Redis using JSON serialization. | |
|[HttpSession with Simple Redis`SessionRepository`](https://github.com/ Spring-projects/ Spring-session/tree/main/ Spring-session-samples/ Spring-session-sample-boot-redis-simple)|Demonstrates how to use Spring Session to replace the `HttpSession` with Redis using `RedisSessionRepository`.| |
|[Spring Session with MongoDB Repositories (servlet-based)](https://github.com/spring-projects/spring-session/tree/main/spring-session-samples/spring-session-sample-boot-mongodb-traditional)| Demonstrates how to back Spring Session with traditional MongoDB repositories. | [Spring Session with MongoDB Repositories](guides/boot-mongo.html) |
|[Spring Session with MongoDB Repositories (reactive)](https://github.com/spring-projects/spring-session/tree/main/spring-session-samples/spring-session-sample-boot-mongodb-reactive)| Demonstrates how to back Spring Session with reactive MongoDB repositories. | [Spring Session with MongoDB Repositories](guides/boot-mongo.html) |
|来源| Description | Guide |
|-----------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|--------------------------------------------------------------|
|[HttpSession with Redis](https://github.com/spring-projects/spring-session/tree/main/spring-session-samples/spring-session-sample-javaconfig-redis)| Demonstrates how to use Spring Session to replace the `HttpSession` with Redis. | [HttpSession with Redis Guide](guides/java-redis.html) |
|[HttpSession with JDBC](https://github.com/spring-projects/spring-session/tree/main/spring-session-samples/spring-session-sample-javaconfig-jdbc)|Demonstrates how to use Spring Session to replace the `HttpSession` with a relational database store.| [HttpSession with JDBC Guide](guides/java-jdbc.html) |
|[Hazelcast 的 HttpSession](https://github.com/spring-projects/spring-session/tree/main/spring-session-samples/spring-session-sample-javaconfig-hazelcast)| Demonstrates how to use Spring Session to replace the `HttpSession` with Hazelcast. |[HttpSession with Hazelcast Guide](guides/java-hazelcast.html)|
|[自定义 cookie](https://github.com/spring-projects/spring-session/tree/main/spring-session-samples/spring-session-sample-javaconfig-custom-cookie)| Demonstrates how to use Spring Session and customize the cookie. | [Custom Cookie Guide](guides/java-custom-cookie.html) |
|[Spring Security](https://github.com/spring-projects/spring-session/tree/main/spring-session-samples/spring-session-sample-javaconfig-security)| Demonstrates how to use Spring Session with an existing Spring Security application. | [Spring Security Guide](guides/java-security.html) |
|[REST](https://github.com/spring-projects/spring-session/tree/main/spring-session-samples/spring-session-sample-javaconfig-rest)|Demonstrates how to use Spring Session in a REST application to support authenticating with a header.| [REST Guide](guides/java-rest.html) |
|来源| Description | Guide |
|--------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|-----------------------------------------------------|
|[HttpSession with Redis](https://github.com/spring-projects/spring-session/tree/main/spring-session-samples/spring-session-sample-xml-redis)| Demonstrates how to use Spring Session to replace the `HttpSession` with a Redis store. |[HttpSession with Redis Guide](guides/xml-redis.html)|
|[HttpSession with JDBC](https://github.com/spring-projects/spring-session/tree/main/spring-session-samples/spring-session-sample-xml-jdbc)|Demonstrates how to use Spring Session to replace the `HttpSession` with a relational database store.| [HttpSession with JDBC Guide](guides/xml-jdbc.html) |
|来源| Description |Guide|
|------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------|-----|
|[Hazelcast](https://github.com/spring-projects/spring-session/tree/main/spring-session-samples/spring-session-sample-misc-hazelcast)|Demonstrates how to use Spring Session with Hazelcast in a Java EE application.| |
\ No newline at end of file
# Spring 安全集成
Spring Session 提供了具有 Spring 安全性的集成。
## Spring 安全 Remember-Me 支持
Spring Session 提供了与[Spring Security’s Remember-me Authentication](https://docs.spring.io/spring-security/site/docs/5.6.2/reference/html5/#servlet-rememberme)的集成。支持:
* 更改会话过期长度
* 确保会话 cookie 在`Integer.MAX_VALUE`处过期。cookie 过期时间被设置为可能的最大值,因为只有在创建会话时才设置 cookie 过期时间。如果将其设置为与会话到期日相同的值,则会话将在用户使用会话时获得更新,但不会更新 cookie 到期日(导致过期时间固定)。
要在 Java 配置中配置具有 Spring 安全性的 Spring Session ,可以使用以下清单作为指导:
```
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// ... additional configuration ...
.rememberMe((rememberMe) -> rememberMe
.rememberMeServices(rememberMeServices())
);
}
@Bean
public SpringSessionRememberMeServices rememberMeServices() {
SpringSessionRememberMeServices rememberMeServices =
new SpringSessionRememberMeServices();
// optionally customize
rememberMeServices.setAlwaysRemember(true);
return rememberMeServices;
}
```
基于 XML 的配置类似于以下内容:
```
<security:http>
<!-- ... -->
<security:form-login />
<security:remember-me services-ref="rememberMeServices"/>
</security:http>
<bean id="rememberMeServices"
class="org.springframework.session.security.web.authentication.SpringSessionRememberMeServices"
p:alwaysRemember="true"/>
```
## Spring 安全并发会话控制
Spring Session 提供与 Spring 安全性的集成,以支持其并发的会话控制。这允许限制单个用户可以并发的活动会话的数量,但是,与默认的 Spring 安全支持不同,这也适用于集群环境。这是通过提供 Spring Security 的`SessionRegistry`接口的自定义实现来完成的。
当使用 Spring Security 的 Java Config DSL 时,你可以通过`SessionManagementConfigurer`配置自定义`SessionRegistry`,如下所示:
```
@Configuration
public class SecurityConfiguration<S extends Session> extends WebSecurityConfigurerAdapter {
@Autowired
private FindByIndexNameSessionRepository<S> sessionRepository;
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
// other config goes here...
.sessionManagement((sessionManagement) -> sessionManagement
.maximumSessions(2)
.sessionRegistry(sessionRegistry())
);
// @formatter:on
}
@Bean
public SpringSessionBackedSessionRegistry<S> sessionRegistry() {
return new SpringSessionBackedSessionRegistry<>(this.sessionRepository);
}
}
```
这假定你还配置了 Spring Session ,以提供一个`FindByIndexNameSessionRepository`,返回`Session`实例。
当使用 XML 配置时,它看起来类似于以下清单:
```
<security:http>
<!-- other config goes here... -->
<security:session-management>
<security:concurrency-control max-sessions="2" session-registry-ref="sessionRegistry"/>
</security:session-management>
</security:http>
<bean id="sessionRegistry"
class="org.springframework.session.security.SpringSessionBackedSessionRegistry">
<constructor-arg ref="sessionRepository"/>
</bean>
```
这假定你的 Spring Session `SessionRegistry` Bean 被称为`sessionRegistry`,这是所有`SpringHttpSessionConfiguration`子类使用的名称。
## 限制
Spring session 的 Spring security 的`SessionRegistry`接口的实现不支持`getAllPrincipals`方法,因为无法通过使用 Spring session 来检索此信息。 Spring Security 从不调用此方法,因此这只会影响访问`SessionRegistry`本身的应用程序。
\ No newline at end of file
# 升级到 2.x
有了新的主要版本, Spring Session 团队利用这个机会进行了一些非被动的更改。这些更改的重点是改进和协调 Spring Session 的 API,以及删除不受欢迎的组件。
## 基线更新
Spring Session 2.0 需要 Java8 和 Spring Framework5.0 作为基线,因为其整个代码库现在基于 Java8 源代码。有关升级 Spring 框架的更多信息,请参见[Upgrading to Spring Framework 5.x](https://github.com/spring-projects/spring-framework/wiki/Upgrading-to-Spring-Framework-5.x)
## 替换和移除模块
作为项目拆分模块的一部分,现有的`spring-session`已被`spring-session-core`模块所取代。`spring-session-core`模块只保存公共的一组 API 和组件,而其他模块则包含适当的`SessionRepository`的实现以及与该数据存储相关的功能。这适用于几个现有的模块,这些模块以前是一个简单的依赖聚合器助手模块。通过新的模块设置,以下模块实际进行了实现:
* Spring MongoDB 的会话
* Spring Redis 会议
* Spring Session JDBC
* Spring 会议 Hazelcast
此外,从主项目存储库中删除了以下内容:
* Spring Session 数据 Gemfire
* [`spring-session-data-geode`](https://github.com/ Spring-projects/ Spring-session-data-geode)
## 替换和删除包、类和方法
对包、类和方法进行了以下更改:
* `ExpiringSession`API 已合并到`Session`API 中。
* 已经对`Session`API 进行了增强,以充分利用 Java8。
* `Session`API 已通过`changeSessionId`支持进行了扩展。
* 已经更新了`SessionRepository`API,以更好地与 Spring 数据方法命名约定保持一致。
* 如果没有底层`Session`对象,`AbstractSessionEvent`及其子类将不再可构造。
* `RedisOperationsSessionRepository`使用的 Redis 命名空间现在是完全可配置的,而不是部分可配置的。
* 已更新了 Redis 配置支持,以避免注册 Spring Session 特定的`RedisTemplate` Bean。
* 已更新了 JDBC 配置支持,以避免注册 Spring Session 特定的`JdbcTemplate` Bean。
* 以前不推荐的类和方法已在整个代码库中被删除。
## 支持度下降
作为对`HttpSessionStrategy`的更改及其与来自反应性世界的对应物的对齐的一部分,对在单个浏览器实例中管理多个用户会话的支持已被删除。正在考虑在未来的版本中引入一个新的 API 来取代此功能。
\ No newline at end of file
# WebSession 集成
Spring Session 提供了与 Spring WebFlux 的`WebSession`的透明集成。这意味着你可以使用 Spring Session 支持的实现来切换`WebSession`实现。
## 为什么要进行会话和 WebSession?
我们已经提到, Spring Session 提供了与 Spring WebFlux 的`WebSession`的透明集成,但是我们从中得到了什么好处呢?与`HttpSession`一样, Spring Session 使得在不绑定到特定于应用程序容器的解决方案的情况下支持[群集会话](#websession-redis)变得非常简单。
## 与 Redis 的 WebSession
使用 Spring session with`WebSession`是通过注册一个`WebSessionManager`实现来启用的,该实现由 Spring session 的`ReactiveSessionRepository`支持。 Spring 配置负责创建一个`WebSessionManager`,该实现用 Spring Session 支持的实现替换`WebSession`实现。要做到这一点,请添加以下 Spring 配置:
```
@EnableRedisWebSession (1)
public class SessionConfiguration {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(); (2)
}
}
```
|**1**|`@EnableRedisWebSession`注释创建了一个名为`webSessionManager`的 Spring Bean。这个 Bean 实现了`WebSessionManager`<br/>这就是负责替换要由 Spring Session 支持的`WebSession`实现的内容。<br/>在这个实例中, Spring Session 是由 Redis 支持的。|
|-----|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|我们创建一个`RedisConnectionFactory`将 Spring Session 连接到 Redis 服务器。<br/>我们将连接配置为在默认端口(6379)<br/>上连接到 localhost。有关配置 Spring 数据 Redis 的更多信息,请参见[参考文献](https://docs.spring.io/spring-data/data-redis/docs/2.6.2/reference/html/)。|
## WebSession 集成如何工作
与 Servlet API 及其`HttpSession`相比, Spring Session 与 Spring WebFlux 及其`WebSession`集成要容易得多。 Spring WebFlux 提供了`WebSessionStore`API,该 API 提供了用于持久化`WebSession`的策略。
| |本节描述 Spring Session 如何使用`WebSession`提供透明的集成。我们提供这些内容,这样你就可以了解幕后发生了什么。这个功能已经集成了,你不需要自己实现这个逻辑。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
首先,我们创建一个自定义的`SpringSessionWebSession`,它将委托给 Spring Session 的`Session`。它看起来是这样的:
```
public class SpringSessionWebSession implements WebSession {
enum State {
NEW, STARTED
}
private final S session;
private AtomicReference<State> state = new AtomicReference<>();
SpringSessionWebSession(S session, State state) {
this.session = session;
this.state.set(state);
}
@Override
public void start() {
this.state.compareAndSet(State.NEW, State.STARTED);
}
@Override
public boolean isStarted() {
State value = this.state.get();
return (State.STARTED.equals(value)
|| (State.NEW.equals(value) && !this.session.getAttributes().isEmpty()));
}
@Override
public Mono<Void> changeSessionId() {
return Mono.defer(() -> {
this.session.changeSessionId();
return save();
});
}
// ... other methods delegate to the original Session
}
```
接下来,我们创建一个自定义`WebSessionStore`,它将委托给`ReactiveSessionRepository`,并将`Session`封装到自定义`WebSession`实现中,如下所示:
```
public class SpringSessionWebSessionStore<S extends Session> implements WebSessionStore {
private final ReactiveSessionRepository<S> sessions;
public SpringSessionWebSessionStore(ReactiveSessionRepository<S> reactiveSessionRepository) {
this.sessions = reactiveSessionRepository;
}
// ...
}
```
要被 Spring WebFlux 检测到,此自定义`WebSessionStore`需要在`ApplicationContext`中注册为 Bean 名为`webSessionManager`的 Bean。有关 Spring WebFlux 的更多信息,请参见[Spring Framework Reference Documentation](https://docs.spring.io/spring-framework/docs/5.3.16/reference/html/web-reactive.html)
\ No newline at end of file
# WebSocket 整合
Spring Session 提供了 Spring 的 WebSocket 支持的透明集成。
| |Spring Session 的 WebSocket 支持仅对 Spring 的 WebSocket 支持有效。<br/>具体来说,它不能直接使用[JSR-356](https://www.jcp.org/en/jsr/detail?id=356),因为 JSR-356 没有拦截传入 WebSocket 消息的机制。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
## 为什么要使用会话和 WebSockets?
那么,当我们使用 WebSockets 时,为什么需要 Spring Session 呢?
考虑一个电子邮件应用程序,它的大部分工作都是通过 HTTP 请求完成的。然而,其中还嵌入了一个聊天应用程序,该应用程序可以在 WebSocket API 上工作。如果用户正在积极地与某人聊天,我们不应该超时`HttpSession`,因为这将是一个非常糟糕的用户体验。然而,这正是[JSR-356](https://java.net/jira/browse/WEBSOCKET_SPEC-175)所做的。
另一个问题是,根据 JSR-356,如果`HttpSession`超时,则任何用`HttpSession`创建的 WebSocket 和经过身份验证的用户都应该被强制关闭。这意味着,如果我们在应用程序中积极地聊天,而不使用 HttpSession,那么我们也会断开与我们的对话的连接。
## WebSocket 用法
[ WebSocket Sample](samples.html#samples)提供了如何将 Spring Session 与 WebSockets 集成的工作示例。你可以遵循下面几个标题中描述的集成的基本步骤,但我们鼓励你在与自己的应用程序集成时遵循详细的 WebSocket 指南。
### `HttpSession`积分
在使用 WebSocket 集成之前,你应该确保首先有[`HttpSession`集成](http-session.html#httpsession)在工作。
#### Spring 配置
在典型的 Spring WebSocket 应用程序中,你将实现`WebSocketMessageBrokerConfigurer`。例如,配置可能如下所示:
```
@Configuration
@EnableScheduling
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/messages").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/app");
}
}
```
我们可以更新配置以使用 Spring Session 的 WebSocket 支持。下面的示例展示了如何做到这一点:
SRC/main/java/samples/config/websocketconfig.java
```
@Configuration
@EnableScheduling
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractSessionWebSocketMessageBrokerConfigurer<Session> { (1)
@Override
protected void configureStompEndpoints(StompEndpointRegistry registry) { (2)
registry.addEndpoint("/messages").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/app");
}
}
```
要连接 Spring Session 支持,我们只需要更改两件事:
|**1**|而不是实现`WebSocketMessageBrokerConfigurer`,我们扩展`AbstractSessionWebSocketMessageBrokerConfigurer`|
|-----|-----------------------------------------------------------------------------------------------------------------------|
|**2**|我们将`registerStompEndpoints`方法重命名为`configureStompEndpoints`|
`AbstractSessionWebSocketMessageBrokerConfigurer`在幕后做什么?
* `WebSocketConnectHandlerDecoratorFactory`作为`WebSocketHandlerDecoratorFactory`添加到`WebSocketTransportRegistration`。这确保了一个包含`WebSocketSession`的自定义`SessionConnectEvent`被触发。当 Spring Session 结束时,要结束任何 WebSocket 仍处于打开状态的连接,`WebSocketSession`是必需的。
* `SessionRepositoryMessageInterceptor`作为`HandshakeInterceptor`添加到每个`StompWebSocketEndpointRegistration`。这确保将`Session`添加到 WebSocket 属性中,以允许更新上次访问的时间。
* `SessionRepositoryMessageInterceptor`作为`ChannelInterceptor`添加到我们的入站`ChannelRegistration`中。这确保了每次接收入站消息时,都会更新 Spring Session 的最后一次访问时间。
* `WebSocketRegistryListener`被创建为 Spring Bean。这确保了我们将所有`Session`ID 映射到相应的 WebSocket 连接。通过维护此映射,我们可以在 Spring Session 结束时关闭所有 WebSocket 连接。
\ No newline at end of file
# Spring 带有自定义 cookie 的会话-WebFlux
本指南描述了如何配置 Spring Session 以在基于 WebFlux 的应用程序中使用自定义 Cookie。该指南假定你已经使用所选的数据存储在项目中设置了 Spring Session 。例如,[HttpSession with Redis ](./boot-redis.html)
| |你可以在[WebFlux 自定义 Cookie 示例应用程序](#webflux-custom-cookie-sample)中找到完整的指南。|
|---|------------------------------------------------------------------------------------------------------------------|
[Index](../index.html)
## Spring 引导配置
一旦设置了 Spring Session ,就可以通过将`WebSessionIdResolver`公开为 Spring Bean 来自定义会话 cookie 的编写方式。 Spring 默认情况下,会话使用`CookieWebSessionIdResolver`。在使用`@EnableRedisHttpSession`之类的配置时,将`WebSessionIdResolver`公开为 Spring Bean 会增强现有的配置。下面的示例展示了如何自定义 Spring Session 的 cookie:
```
@Bean
public WebSessionIdResolver webSessionIdResolver() {
CookieWebSessionIdResolver resolver = new CookieWebSessionIdResolver();
resolver.setCookieName("JSESSIONID"); (1)
resolver.addCookieInitializer((builder) -> builder.path("/")); (2)
resolver.addCookieInitializer((builder) -> builder.sameSite("Strict")); (3)
return resolver;
}
```
|**1**|我们将 cookie 的名称自定义为`JSESSIONID`。|
|-----|--------------------------------------------------------------------------------------------|
|**2**|我们将 cookie 的路径自定义为`/`(而不是上下文根的默认值)。|
|**3**|我们将`SameSite`cookie 指令自定义为`Strict`。|
## `webflux-custom-cookie`示例应用程序
本节介绍如何使用`webflux-custom-cookie`示例应用程序。
### 运行`webflux-custom-cookie`示例应用程序
你可以通过获取[源代码](https://github.com/spring-projects/spring-session/archive/main.zip)并调用以下命令来运行示例:
```
$ ./gradlew :spring-session-sample-boot-webflux-custom-cookie:bootRun
```
| |要使示例工作,你必须在 localhost 上[安装 Redis2.8+](https://redis.io/download)并使用默认端口(6379)运行它。<br/>或者,你可以更新`RedisConnectionFactory`以指向 Redis 服务器。<br/>另一个选项是使用[Docker](https://www.docker.com/)在 localhost 上运行 Redis。详细说明见[Docker Redis 存储库](https://hub.docker.com/_/redis/)。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
现在你应该可以在[http://localhost:8080/](http://localhost:8080/)上访问应用程序了。
### 探索`webflux-custom-cookie`示例应用程序
现在你可以使用该应用程序了。请在表格中填写以下信息:
* **属性名称:** *用户 Name *
* **属性值:** *罗布*
现在点击**设置属性**按钮。现在你应该可以看到表中显示的值了。
如果查看应用程序的 cookie,可以看到 cookie 已保存到自定义名称`JSESSIONID`
\ No newline at end of file
# 最新更新
还请检查 Spring Session BOM[发行说明](https://github.com/spring-projects/spring-session-bom/wiki#release-notes),以获取新的和值得注意的功能的列表,以及每个版本的升级说明。
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册