如何在WebSocket控制器中获取当前已登录的认证用户
问题原因
WebSocket STOMP协议的握手阶段默认不会自动同步Spring Security的HTTP认证上下文到WebSocket会话中,导致后续消息处理时无法获取到已认证的Principal对象。
解决步骤
1. 修改WebSocket配置,添加认证信息同步逻辑
修改你的WebSocketConfig类,增加握手拦截器、入站通道拦截器,配置用户目的地前缀:
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/connect") // 生产环境请替换为具体前端域名,不要使用通配符* .setAllowedOrigins("*") // 同步HTTP Session中的认证信息到WebSocket会话 .addInterceptors(new HttpSessionHandshakeInterceptor()); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { // 配置用户消息前缀,配合convertAndSendToUser使用 registry.setUserDestinationPrefix("/user"); registry.enableSimpleBroker("/topic/messages", "/user"); registry.setApplicationDestinationPrefixes("/ws"); } // 无状态JWT认证场景可添加此拦截器,从请求头解析token完成认证 @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())) { // 从连接头获取token,自行完成认证后设置用户信息 // String token = accessor.getFirstNativeHeader("Authorization"); // Authentication auth = authenticationManager.authenticate(自定义认证对象); // accessor.setUser(auth); // SecurityContextHolder.getContext().setAuthentication(auth); } return message; } }); } }
2. 调整Spring Security配置
确保WebSocket端点的权限、跨域规则符合要求:
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth // 允许已认证用户访问WebSocket端点 .requestMatchers("/connect").authenticated() .anyRequest().authenticated() ) // 跨域配置要和WebSocket配置保持一致 .cors(cors -> cors.configurationSource(corsConfigurationSource())) // Session策略根据你的认证方式调整,Cookie认证保留默认IF_REQUIRED即可 .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)); return http.build(); } @Bean CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); // 生产环境替换为具体前端域名 configuration.setAllowedOrigins(List.of("*")); configuration.setAllowedMethods(List.of("*")); configuration.setAllowedHeaders(List.of("*")); // Cookie认证场景需要开启允许携带凭证 configuration.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; } }
3. 前端连接时携带认证凭证
- Cookie认证场景必须开启
withCredentials,才能将Cookie中的认证信息传递到后端 - Token认证场景需要在STOMP连接头中携带认证Token
import SockJS from 'sockjs-client'; import { Stomp } from '@stomp/stompjs'; const socket = new SockJS('http://你的后端地址/connect', {}, { withCredentials: true // Cookie认证必须开启 }); const stompClient = Stomp.over(socket); stompClient.connect({ // Token认证场景在此处添加认证头 // 'Authorization': 'Bearer ' + 你的Token }, (frame) => { console.log('连接成功'); stompClient.send("/ws/start", {}, {}); });
验证
完成上述配置后,@MessageMapping标注方法中的Principal入参即可正常获取,也可以通过SecurityContextHolder.getContext().getAuthentication()拿到认证信息。
注意:如果开启了allowCredentials=true,WebSocket和Security的跨域配置中不能使用通配符*作为允许来源,必须填写具体的前端域名
内容的提问来源于stack exchange,提问作者Gloria
相关产品推荐
相关产品推荐

