Spring Security下STOMP使用:stompjs带Token连接遇401问题
在Spring Security环境下使用STOMP携带Token认证的解决方案
你当前的stompjs代码存在参数传递错误,导致Authorization头未被正确添加到STOMP CONNECT帧中,另外需要区分WebSocket握手阶段的请求头和STOMP协议帧的头信息,两者处理方式不同,以下是具体解决步骤:
1. 修正stompjs的connect参数写法
你的代码错误地将headers嵌套在headers字段中,stompjs的connect方法第一个参数直接接收头信息对象,无需额外嵌套:
// 修正后的代码 let client = Stomp.client("ws://192.168.181.77:3001/ws"); client.connect( { Authorization: "Bearer TOKEN" // 直接传递头信息,不要嵌套在headers里 }, () => { console.log("client ok"); }, () => { console.log("client not ok"); } );
2. 处理WebSocket握手阶段的认证(若Spring Security在握手时校验)
如果你的Spring Security是在WebSocket握手请求阶段就校验Token(而非STOMP CONNECT帧),需要在创建WebSocket连接时设置请求头,此时不能用Stomp.client,需手动创建WebSocket实例并添加头:
// 手动创建带请求头的WebSocket const socket = new WebSocket("ws://192.168.181.77:3001/ws", { headers: { Authorization: "Bearer TOKEN" } }); // 基于这个WebSocket创建Stomp客户端 let client = Stomp.over(socket); client.connect( {}, // 握手阶段已传Token,这里可空或补充其他STOMP帧头 () => { console.log("client ok"); }, () => { console.log("client not ok"); } );
3. Spring Security端的配置调整
3.1 放行WebSocket端点
确保SecurityFilterChain中放行你的WebSocket端点(/ws),允许OPTIONS预检请求和携带Authorization头:
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/ws/**").permitAll() // 放行WebSocket端点 .anyRequest().authenticated() ) .csrf(csrf -> csrf .ignoringRequestMatchers("/ws/**") // WebSocket无需CSRF校验 ); return http.build(); }
3.2 配置STOMP帧认证(若在STOMP帧中校验)
如果是在STOMP CONNECT帧中校验Token,需配置ChannelInterceptor提取帧中的Authorization头并完成认证:
@Component public class StompAuthInterceptor implements ChannelInterceptor { private final AuthenticationManager authenticationManager; public StompAuthInterceptor(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } @Override public Message<?> preSend(Message<?> message, MessageChannel channel) { StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); if (StompCommand.CONNECT.equals(accessor.getCommand())) { String authHeader = accessor.getFirstNativeHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { String token = authHeader.substring(7); // 替换为你的Token解析逻辑,创建Authentication对象 Authentication authentication = new UsernamePasswordAuthenticationToken(token, token); Authentication authenticated = authenticationManager.authenticate(authentication); SecurityContextHolder.getContext().setAuthentication(authenticated); } } return message; } }
然后在WebSocket配置中注册该拦截器:
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { private final StompAuthInterceptor stompAuthInterceptor; public WebSocketConfig(StompAuthInterceptor stompAuthInterceptor) { this.stompAuthInterceptor = stompAuthInterceptor; } @Override public void configureClientInboundChannel(ChannelRegistration registration) { registration.interceptors(stompAuthInterceptor); } @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic"); config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws").withSockJS(); // 若使用SockJS则添加此配置 } }
关键注意点
- 区分WebSocket握手请求(HTTP请求)和STOMP协议帧:前者是建立WebSocket连接的HTTP请求,后者是连接建立后发送的STOMP消息帧,两者头信息设置方式不同。
- 若使用SockJS,由于其可能降级为XHR等传输方式,手动设置WebSocket头的方式可能失效,此时建议将Token作为查询参数传递(如
ws://192.168.181.77:3001/ws?token=xxx),再在Spring端的握手拦截器中提取查询参数完成认证。
内容的提问来源于stack exchange,提问作者Senith Uthsara
相关产品推荐
相关产品推荐

