Spring Session - WebSocket

本指南描述如何使用 Spring 会话来确保 WebSocket 消息使您的 HttpSession 保持活动状态。

Spring Session 的 WebSocket 支持仅适用于 Spring 的 WebSocket 支持。具体来说,它不适用于直接使用 JSR-356,因为 JSR-356 没有用于拦截传入 WebSocket 消息的机制。

HttpSession Setup

第一步是将 Spring Session 与 HttpSession 集成。这些步骤已在 HttpSession with Redis Guide 中概述。

确保在继续之前,您已将 Spring 会话与 HttpSession 集成。

Spring Configuration

在典型的 Spring WebSocket 应用程序中,您会实现 WebSocketMessageBrokerConfigurer。例如,配置可能类似于以下内容:

/*
 * Copyright 2014-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package docs.websocket;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

/**
 * @author Rob Winch
 */
// tag::class[]
@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");
	}

}
// end::class[]

我们可以更新配置以使用 Spring 会话的 WebSocket 支持。以下示例演示如何操作:

src/main/java/samples/config/WebSocketConfig.java
Unresolved include directive in modules/ROOT/pages/guides/boot-websocket.adoc - include::example$spring-session-samples/spring-session-sample-boot-websocket/src/main/java/sample/config/WebSocketConfig.java[]

连接到 Spring 会话支持时,我们只需做两件事:

1 不实现 WebSocketMessageBrokerConfigurer,而是扩展 AbstractSessionWebSocketMessageBrokerConfigurer
2 registerStompEndpoints 方法重命名为 configureStompEndpoints

`AbstractSessionWebSocketMessageBrokerConfigurer`在幕后执行什么操作?

  • WebSocketConnectHandlerDecoratorFactory 作为一个 WebSocketHandlerDecoratorFactory 添加到 WebSocketTransportRegistration。这将确保会触发一个包含 SessionConnectEvent 的自定义 SessionConnectEvent。在 Spring Session 结束时,WebSocketSession 对于结束任何仍然处于打开状态的 WebSocket 连接是必需的。

  • SessionRepositoryMessageInterceptor 作为一个 HandshakeInterceptor 添加到每个 StompWebSocketEndpointRegistration。这将确保会将 Session 添加到 WebSocket 属性,以更新最新的访问时间。

  • SessionRepositoryMessageInterceptor 作为一个 ChannelInterceptor 添加到我们的入站 ChannelRegistration。这将确保我们每收到一条入站消息就会更新 Spring Session 的上次访问时间。

  • WebSocketRegistryListener 创建为一个 Spring bean。这将确保我们拥有所有 Session ID 到相应 WebSocket 连接的映射。通过维护此映射,我们可以再 Spring Session(HttpSession) 结束时关闭所有 WebSocket 连接。

websocket Sample Application

`websocket`示例应用程序演示了如何在 WebSocket 中使用 Spring Session。

Running the websocket Sample Application

您可以获取 源代码 并调用以下命令运行示例:

$ ./gradlew :spring-session-sample-boot-websocket:bootRun

出于测试会话到期时间的目的,你可能希望在启动应用程序之前添加以下配置属性,将会话到期时间更改为 1 分钟(默认值为 30 分钟):

src/main/resources/application.properties
server.servlet.session.timeout=1m # Session timeout. If a duration suffix is not specified, seconds will be used.

要让示例发挥作用,你必须在 localhost 上 install Redis 2.8+ 并使用默认端口 (6379) 运行它。或者,你可以更新 RedisConnectionFactory 以指向 Redis 服务器。另一个选项是使用 Docker 在 localhost 上运行 Redis。有关详细说明,请参见 Docker Redis repository

您现在应该能够访问 [role="bare"][role="bare"]http://localhost:8080/ 中的应用程序。

Exploring the websocket Sample Application

现在你可以尝试使用该应用程序。使用以下信息进行身份验证:

  • Username rob

  • Password password

现在,单击*Login*按钮。你现在应该以用户*rob*的身份进行身份验证。

打开一个隐身窗口并访问 [role="bare"][role="bare"]http://localhost:8080/

系统会提示你提供一个登录表单。使用以下信息进行身份验证:

  • Username luke

  • Password password

现在从 Rob 发送一条消息给 Luke。消息应该会显示出来。

等待两分钟,然后再次尝试从 Rob 给 Luke 发送一条消息。你可以看到,消息不再发送。

Why two minutes?

Spring Session 在 60 秒后到期,但不能保证在 60 秒内收到来自 Redis 的通知。为了确保在合理的时间范围内关闭套接字,Spring Session 会在每分钟的第 00 秒运行一个后台任务,以强制清除所有过期的会话。这意味着你需要最多等待两分钟,WebSocket 连接才会关闭。

现在重复相同的练习,但是不要等待两分钟,而是在每 30 秒内从每个用户发送一条消息。您可以看到仍会继续发送消息。尝试访问 [role="bare"][role="bare"]http://localhost:8080/,您不会收到再次进行身份验证的提示。这表明会话保持活跃。

只有来自用户的消息才能使会话保持活动状态。这是因为只有来自用户的消息才表示用户活动。收到的消息并不表示活动,因此不会续订会话过期。