Token Authentication

Spring Security OAuth 为基于令牌的安全性提供支持,包括 JSON Web 令牌 (JWT)。你可以在 Web 应用程序中使用它作为身份验证机制,包括通过 WebSocket 进行的 STOMP 交互,如前一节所述(即,通过基于 cookie 的会话维护身份)。

与此同时,基于 cookie 的会话并不总是最合适的(例如在不维护服务器端会话的应用程序或通常使用标头进行验证的移动应用程序中)。

WebSocket 协议,RFC 6455“没有规定服务器在 WebSocket 握手期间验证客户端的任何特定方式。”然而,在实践中,浏览器客户端只能使用标准身份验证标题(即基本的 HTTP 身份验证)或 cookie,并且不能(例如)提供自定义标题。同样,SockJS JavaScript 客户端也没有提供一种方法,可以通过该方法使用 SockJS 传输请求发送 HTTP 标题。请参阅 sockjs-client 问题 196。它允许发送查询参数,你可以用它来发送令牌,但这有其自身的缺点(例如,令牌可能无意中与 URL 一起记录在服务器日志中)。

上述限制适用于基于浏览器的客户端,不适用于基于 Spring Java 的 STOMP 客户端,该客户端确实支持为 WebSocket 和 SockJS 请求发送头。

因此,希望避免使用 cookie 的应用程序可能没有任何很好的替代方案在 HTTP 协议级别进行验证。除了使用 cookie,他们可能更愿意在 STOMP 消息协议级别通过标头进行验证。这样做需要两个简单的步骤:

  1. 在连接时使用 STOMP 客户端来传递身份验证标头。

  2. 使用 ChannelInterceptor 处理身份验证标头。

下面的示例使用服务器端配置来注册一个自定义验证拦截器。请注意,一个拦截器只需验证并在 CONNECT 消息 上设置用户标头。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 之前。最好通过在一个标记为 @Order(Ordered.HIGHEST_PRECEDENCE + 99)WebSocketMessageBrokerConfigurer 的自定义实现中声明自定义拦截器来实现。