Spring Session - WebSocket

本指南介紹如何使用 Spring Session 來確保 WebSocket 訊息保持您的 HttpSession 處於活動狀態。

Spring Session 的 WebSocket 支援僅適用於 Spring 的 WebSocket 支援。具體來說,它不直接支援使用 JSR-356,因為 JSR-356 沒有攔截傳入 WebSocket 訊息的機制。

HttpSession 設定

第一步是將 Spring Session 與 HttpSession 整合。這些步驟已在使用 Redis 的 HttpSession 指南中概述。

請確保在繼續之前已將 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 我們擴充套件 AbstractSessionWebSocketMessageBrokerConfigurer,而不是實現 WebSocketMessageBrokerConfigurer
2 我們將 registerStompEndpoints 方法重新命名為 configureStompEndpoints

AbstractSessionWebSocketMessageBrokerConfigurer 在幕後做了什麼?

  • WebSocketConnectHandlerDecoratorFactory 作為 WebSocketHandlerDecoratorFactory 新增到 WebSocketTransportRegistration。這確保觸發一個包含 WebSocketSession 的自定義 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 示例應用

websocket 示例應用演示瞭如何將 Spring Session 與 WebSockets 一起使用。

執行 websocket 示例應用

您可以透過獲取原始碼並執行以下命令來執行示例

$ ./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 上安裝 Redis 2.8+ 並使用預設埠 (6379) 執行它。或者,您可以更新 RedisConnectionFactory 以指向 Redis 伺服器。另一種選擇是使用Docker 在 localhost 上執行 Redis。有關詳細說明,請參閱Docker Redis 倉庫

現在您應該能夠透過 localhost:8080/ 訪問該應用

探索 websocket 示例應用

現在您可以嘗試使用該應用。使用以下資訊進行身份驗證

  • 使用者名稱 rob

  • 密碼 password

現在點選登入按鈕。您現在應該已以使用者 rob 的身份透過身份驗證。

開啟隱身視窗並訪問 localhost:8080/

系統會提示您填寫登入表單。使用以下資訊進行身份驗證

  • 使用者名稱 luke

  • 密碼 password

現在從 rob 傳送訊息給 luke。訊息應該會顯示。

等待兩分鐘,然後再次嘗試從 rob 傳送訊息給 luke。您會看到訊息不再發送。

為什麼是兩分鐘?

Spring Session 在 60 秒內過期,但無法保證在 60 秒內收到 Redis 的通知。為了確保套接字在合理的時間內關閉,Spring Session 每分鐘的 00 秒會執行一個後臺任務,強制清理所有過期的會話。這意味著您最多需要等待兩分鐘,WebSocket 連線才會關閉。

您現在可以嘗試訪問 localhost:8080/。系統會再次提示您進行身份驗證。這表明會話已正常過期。

現在重複相同的練習,但不是等待兩分鐘,而是每隔 30 秒從每個使用者傳送一條訊息。您會看到訊息持續傳送。嘗試訪問 localhost:8080/。系統不會再次提示您進行身份驗證。這表明會話保持活動狀態。

只有使用者傳送的訊息才能保持會話活動。這是因為只有來自使用者的訊息才表示使用者活動。接收到的訊息不表示活動,因此不會更新會話過期時間。