如何获取在ChannelInterceptor中设置的WebSocket Principal?Principal为空
问题分析与解决方案
核心原因
你在ChannelInterceptor的preSend方法中修改了StompHeaderAccessor,但未处理消息的可变性:Spring的MessageHeaderAccessor默认是不可变状态,修改后的内容不会自动同步到原始消息中,导致后续@MessageMapping方法无法获取到设置的Principal。此外,处理WebSocket握手阶段的用户认证,更推荐使用专门的HandshakeInterceptor,语义更清晰且能确保Principal绑定到会话。
方案一:修复现有ChannelInterceptor实现
修改CustomHandshakeInterceptor,标记Accessor为可变并重新构建消息:
@Service public class CustomHandshakeInterceptor implements ChannelInterceptor { private JwtUtil jwtUtil; public CustomHandshakeInterceptor(JwtUtil jwtUtil) { this.jwtUtil = jwtUtil; } @Override public Message<?> preSend(Message<?> message, MessageChannel channel) { StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); if(StompCommand.CONNECT.equals(accessor.getCommand())){ Object raw = message.getHeaders().get(SimpMessageHeaderAccessor.NATIVE_HEADERS); if(raw instanceof Map){ Object token = ((Map) raw).get("JWT_TOKEN"); if(token instanceof ArrayList){ String jwtToken = ((ArrayList<?>) token).get(0).toString(); System.out.println(jwtToken); accessor.setUser(() -> jwtUtil.extractUsername(jwtToken)); } } // 标记Accessor为可变,确保修改生效 accessor.setLeaveMutable(true); // 基于修改后的Accessor重新构建消息 Message<?> newMessage = MessageBuilder.createMessage(message.getPayload(), accessor.getMessageHeaders()); System.out.println("FROM INTERCEPTOR " + accessor.getUser().getName()); return newMessage; } return message; } }
方案二:改用HandshakeInterceptor(推荐)
HandshakeInterceptor是Spring专门为WebSocket握手阶段设计的拦截器,能更可靠地绑定Principal到会话:
1. 实现自定义握手拦截器
@Service public class CustomHandshakeInterceptor implements HandshakeInterceptor { private JwtUtil jwtUtil; public CustomHandshakeInterceptor(JwtUtil jwtUtil) { this.jwtUtil = jwtUtil; } @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { // 从请求头获取JWT_TOKEN List<String> tokenHeaders = request.getHeaders().get("JWT_TOKEN"); if (tokenHeaders != null && !tokenHeaders.isEmpty()) { String jwtToken = tokenHeaders.get(0); String username = jwtUtil.extractUsername(jwtToken); // 将Principal存入会话属性,后续自动绑定 attributes.put(Principal.class.getName(), (Principal) () -> username); System.out.println("FROM HANDSHAKE INTERCEPTOR " + username); } return true; } @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { // 无需额外处理 } }
2. 修改SocketConfig注册拦截器
@Configuration @EnableWebSocketMessageBroker public class SocketConfig implements WebSocketMessageBrokerConfigurer { private CustomHandshakeInterceptor handshakeInterceptor; public SocketConfig(CustomHandshakeInterceptor handshakeInterceptor) { this.handshakeInterceptor = handshakeInterceptor; } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/stomp") .setAllowedOrigins("http://localhost:3000") .addInterceptors(handshakeInterceptor) // 注册握手拦截器 .withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("queue"); registry.setApplicationDestinationPrefixes("/app"); registry.setUserDestinationPrefix("/users"); } // 移除原configureClientInboundChannel配置 }
优化Controller获取方式
使用方案二后,Controller可以直接注入Principal参数,更简洁:
@MessageMapping("/private") public void message(Principal principal, @Payload Message message){ System.out.println(principal.getName()); }
两种方案均可解决Principal为空的问题,方案二更贴合Spring WebSocket的设计规范,避免不必要的性能开销。
内容的提问来源于stack exchange,提问作者ZZBL
相关产品推荐
相关产品推荐

