Spring Boot WebSocket握手拦截器无法获取React端username header问题
解决SockJS+STOMP握手时无法获取username的问题
问题根源
你通过client.connect()传递的header是STOMP协议帧的专属header,并非HTTP握手阶段的HTTP请求头,所以在HandshakeInterceptor里无法读取到。另外跨域场景下,自定义HTTP头会被浏览器预检请求拦截,也会导致参数无法传递。
可行解决方案
方案1:通过URL Query参数传递username
这种方式能在HTTP握手阶段直接拿到参数,适合需要在握手环节就获取用户信息的场景。
前端修改
把username拼接到SockJS的连接URL后:
const socketFactory = () => { // 对username做URL编码,避免特殊字符问题 return new SockJS(`http://localhost:8080/ws?username=${encodeURIComponent(username)}`); }; const client = Stomp.over(socketFactory); // 无需再在connect参数里传递username client.connect({}, () => { if (mounted) { console.log('WebSocket Connected'); setIsConnected(true); setError(''); stompClientRef.current = client; subscriptionRef.current = client.subscribe('/topic/main', (response) => { try { const message = JSON.parse(response.body); setMessages((prev) => [...prev, message]); } catch (err) { console.error('Error parsing message:', err); } }); } });
后端修改
在HandshakeInterceptor中从Query参数提取username:
public class MyHandshakeInterceptor implements HandshakeInterceptor { @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { // 从Query参数获取username并解码 String username = request.getQueryParams().getFirst("username"); if (username != null) { username = URLDecoder.decode(username, StandardCharsets.UTF_8.name()); attributes.put("username", username); System.out.println("username from interceptor: " + username); } else { System.out.println("Username is null"); } return true; } @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { // 空实现即可 } }
方案2:从STOMP Connect帧中获取username
如果不需要在HTTP握手阶段拿到username,而是在STOMP连接建立后获取,这种方式更符合STOMP协议的设计逻辑。
前端保持原有代码
继续在client.connect()的参数中传递username即可:
client.connect( {'username': username}, () => { if (mounted) { console.log('WebSocket Connected'); setIsConnected(true); setError(''); stompClientRef.current = client; subscriptionRef.current = client.subscribe('/topic/main', (response) => { try { const message = JSON.parse(response.body); setMessages((prev) => [...prev, message]); } catch (err) { console.error('Error parsing message:', err); } }); } } );
后端添加STOMP帧拦截器
创建ChannelInterceptor拦截STOMP的CONNECT帧,提取username并存入会话属性:
@Component public class StompConnectInterceptor implements ChannelInterceptor { @Override public Message<?> preSend(Message<?> message, MessageChannel channel) { StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); if (StompCommand.CONNECT.equals(accessor.getCommand())) { String username = accessor.getFirstNativeHeader("username"); if (username != null) { // 将username存入会话属性,后续事件监听可直接获取 accessor.getSessionAttributes().put("username", username); } } return message; } }
配置WebSocket注册拦截器
在WebSocket配置类中注册该拦截器:
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Autowired private StompConnectInterceptor stompConnectInterceptor; @Override public void configureClientInboundChannel(ChannelRegistration registration) { registration.interceptors(stompConnectInterceptor); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws") .setAllowedOrigins("http://localhost:3000") .withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("/topic"); registry.setApplicationDestinationPrefixes("/app"); } }
实现用户加入/离开通知
无论采用哪种方案,都可以通过监听WebSocket会话事件来广播系统消息:
@Component public class WebSocketSessionListener { @Autowired private SimpMessagingTemplate messagingTemplate; // 监听用户连接事件 @EventListener public void onSessionConnect(SessionConnectEvent event) { StompHeaderAccessor accessor = StompHeaderAccessor.wrap(event.getMessage()); String username = accessor.getSessionAttributes().get("username").toString(); messagingTemplate.convertAndSend("/topic/main", new ChatMessage(username + " 加入了聊天", "SYSTEM")); } // 监听用户断开连接事件 @EventListener public void onSessionDisconnect(SessionDisconnectEvent event) { StompHeaderAccessor accessor = StompHeaderAccessor.wrap(event.getMessage()); String username = accessor.getSessionAttributes().get("username").toString(); messagingTemplate.convertAndSend("/topic/main", new ChatMessage(username + " 离开了聊天", "SYSTEM")); } } // 聊天消息实体类示例 public class ChatMessage { private String content; private String type; public ChatMessage(String content, String type) { this.content = content; this.type = type; } // getter和setter方法 public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getType() { return type; } public void setType(String type) { this.type = type; } }
内容的提问来源于stack exchange,提问作者Nishan Kumar Shrestha
相关产品推荐
相关产品推荐

