令牌认证
Spring Security OAuth 提供了基于令牌的安全性支持,包括 JSON Web Token (JWT)。您可以将其用作 Web 应用程序中的身份验证机制,包括前一节中描述的 STOMP over WebSocket 交互(即,通过基于 cookie 的会话保持身份)。
同时,基于 cookie 的会话并不总是最佳选择(例如,在不维护服务器端会话的应用程序中,或在常用头部进行身份验证的移动应用程序中)。
WebSocket 协议,RFC 6455 “并没有规定服务器在 WebSocket 握手期间可以以任何特定方式对客户端进行身份验证。” 然而,在实践中,浏览器客户端只能使用标准身份验证头(即基本 HTTP 身份验证)或 cookies,而不能(例如)提供自定义头。同样,SockJS JavaScript 客户端也不提供在 SockJS 传输请求中发送 HTTP 头的方法。请参见 sockjs-client issue 196。相反,它确实允许发送查询参数,您可以使用这些参数发送令牌,但这也有其自身的缺点(例如,令牌可能会与 URL 一起意外地记录在服务器日志中)。
前述限制适用于基于浏览器的客户端,不适用于基于 Spring Java 的 STOMP 客户端,该客户端确实支持在 WebSocket 和 SockJS 请求中发送头信息。
因此,想要避免使用 cookies 的应用程序在 HTTP 协议层面可能没有任何好的身份验证替代方案。它们可能更愿意在 STOMP 消息协议层面使用 headers 进行身份验证。这样做需要两个简单的步骤:
-
使用 STOMP 客户端在连接时传递认证头。
-
使用
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)
。