Spring Boot 3 + Spring Security 6 WebSocket认证问题求助
Spring Boot 3 + Spring Security 6 WebSocket 认证问题解决方案
核心问题定位
- 添加
@EnableWebSocketSecurity后出现access denied:该注解会启用Spring Security对WebSocket消息的严格认证控制,默认拒绝所有未认证消息,但未正确配置认证规则与上下文关联导致权限拦截。 - CSRF配置后出现请求对象回收错误:WebSocket握手完成后,原始HTTP请求对象会被容器回收,若在消息处理阶段尝试获取该对象就会触发此错误。
分步解决方案
1. 正确配置WebSocket Security规则
使用@EnableWebSocketSecurity时,必须显式定义消息的认证规则,同时确保WebSocket认证与HTTP认证上下文共享:
@Configuration @EnableWebSocket @EnableWebSocketSecurity public class WebSocketsConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(customWebSocketHandler(), "/ws/connect") .setAllowedOrigins("https://your-frontend-domain.com") // 替换为实际跨域域名 .withSockJS(); // 若使用SockJS则保留,否则移除 } @Bean public WebSocketHandler customWebSocketHandler() { return new CustomWebSocketHandler(); } // 配置WebSocket消息认证规则 @Bean public SecurityWebSocketFilterChain webSocketSecurityFilterChain(WebSocketSecurity webSocket) { webSocket .authorizeMessages(messages -> messages .anyMessage().authenticated() // 所有消息需已认证用户发送 ) .csrf(csrf -> csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) // 允许前端从Cookie读取CSRF Token ); return webSocket.build(); } }
2. 修正HTTP Security配置,适配WebSocket握手
确保WebSocket握手端点允许匿名访问(后续消息认证由WebSocket Security控制),同时配置CSRF Token的存储方式:
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/ws/connect/**").permitAll() // 允许WebSocket握手请求匿名访问 .anyRequest().authenticated() ) .csrf(csrf -> csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) // 统一CSRF Token存储 ) // 根据你的认证方式添加配置(如JWT、Session) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)); return http.build(); } }
3. 前端正确传递CSRF Token到握手请求
WebSocket握手是HTTP POST请求,需将CSRF Token放在请求头中,而非WebSocket消息帧里:
import SockJS from 'sockjs-client'; import Stomp from 'stompjs'; // 从Cookie中获取Spring Security生成的XSRF-TOKEN const getCsrfToken = () => { const match = document.cookie.match(/XSRF-TOKEN=([^;]+)/); return match ? match[1] : ''; }; export const initWebSocket = () => { const socket = new SockJS('/ws/connect'); const stompClient = Stomp.over(socket); // 握手时添加CSRF请求头 stompClient.connect( { 'X-XSRF-TOKEN': getCsrfToken() }, (frame) => { console.log('WebSocket连接成功:', frame); stompClient.subscribe('/topic/notify', (msg) => { // 处理收到的消息 console.log('收到消息:', JSON.parse(msg.body)); }); }, (error) => { console.error('WebSocket连接失败:', error); } ); return stompClient; };
4. 避免在消息处理阶段访问已回收的HTTP请求对象
不要在WebSocket消息处理逻辑中尝试获取HttpServletRequest,应从WebSocketSession的属性中获取认证信息:
public class CustomWebSocketHandler extends TextWebSocketHandler { @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { // 从Session属性中获取认证对象(由Spring Security自动注入) Authentication auth = (Authentication) session.getAttributes().get(WebSocketSecurityConstants.AUTHENTICATION_ATTR_NAME); if (auth == null) { session.close(CloseStatus.NOT_ACCEPTABLE); return; } // 使用认证信息处理业务逻辑 String username = auth.getName(); System.out.printf("用户[%s]发送消息: %s%n", username, message.getPayload()); // 回复消息示例 session.sendMessage(new TextMessage("已收到你的消息: " + message.getPayload())); } }
关键注意事项
@EnableWebSocketSecurity会完全接管WebSocket的安全控制,必须显式配置authorizeMessages,否则默认拒绝所有消息。- 若使用JWT认证,需额外配置
WebSocketAuthenticationManager,将握手请求头中的JWT Token解析为认证对象。 - 跨域配置需严格匹配前端域名,避免使用
*(生产环境禁止)。
内容的提问来源于stack exchange,提问作者PedroArs99
相关产品推荐
相关产品推荐

