令牌認證

Spring Security OAuth 提供令牌安全支援,包括 JSON Web Token (JWT)。您可以在 Web 應用程式(包括 WebSocket 上的 STOMP 互動)中使用它作為認證機制,如上一節所述(即透過基於 cookie 的會話維護身份)。

同時,基於 cookie 的會話並不總是最佳選擇(例如,在不維護伺服器端會話的應用程式中,或在通常使用請求頭進行認證的移動應用程式中)。

WebSocket 協議,RFC 6455“沒有規定伺服器在 WebSocket 握手期間可以認證客戶端的任何特定方式。”然而,在實踐中,瀏覽器客戶端只能使用標準的認證頭(即基本 HTTP 認證)或 cookie,而不能(例如)提供自定義頭。同樣,SockJS JavaScript 客戶端不提供透過 SockJS 傳輸請求傳送 HTTP 頭的方式。參見sockjs-client issue 196。相反,它允許傳送可用於傳送令牌的查詢引數,但這有其自身的缺點(例如,令牌可能會意外地與 URL 一起記錄在伺服器日誌中)。

上述限制適用於基於瀏覽器的客戶端,不適用於基於 Spring Java 的 STOMP 客戶端,後者支援透過 WebSocket 和 SockJS 請求傳送頭。

因此,希望避免使用 cookie 的應用程式可能在 HTTP 協議級別沒有好的認證替代方案。他們可能更傾向於在 STOMP 訊息協議級別使用請求頭進行認證。這樣做需要兩個簡單的步驟:

  1. 使用 STOMP 客戶端在連線時傳遞認證頭。

  2. 使用 ChannelInterceptor 處理認證頭。

下面的示例使用伺服器端配置註冊自定義認證攔截器。請注意,攔截器只需要認證並在 CONNECT Message 上設定使用者頭。Spring 會記錄並儲存已認證的使用者,並將其與同一會話上的後續 STOMP 訊息關聯起來。以下示例展示瞭如何註冊自定義認證攔截器:

  • Java

  • Kotlin

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {

	@Override
	public void configureClientInboundChannel(ChannelRegistration registration) {
		registration.interceptors(new ChannelInterceptor() {
			@Override
			public Message<?> preSend(Message<?> message, MessageChannel channel) {
				StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
				if (StompCommand.CONNECT.equals(accessor.getCommand())) {
					// Access authentication header(s) and invoke accessor.setUser(user)
				}
				return message;
			}
		});
	}
}
@Configuration
@EnableWebSocketMessageBroker
class WebSocketConfiguration : WebSocketMessageBrokerConfigurer {

	override fun configureClientInboundChannel(registration: ChannelRegistration) {
		registration.interceptors(object : ChannelInterceptor {
			override fun preSend(message: Message<*>, channel: MessageChannel): Message<*> {
				val accessor = MessageHeaderAccessor.getAccessor(message,
					StompHeaderAccessor::class.java)
				if (StompCommand.CONNECT == accessor!!.command) {
					// Access authentication header(s) and invoke accessor.setUser(user)
				}
				return message
			}
		})
	}
}

此外,請注意,目前在使用 Spring Security 對訊息進行授權時,需要確保認證 ChannelInterceptor 配置位於 Spring Security 的配置之前。最好透過在自己的 WebSocketMessageBrokerConfigurer 實現中宣告自定義攔截器並使用 @Order(Ordered.HIGHEST_PRECEDENCE + 99) 進行標記來實現這一點。