You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何获取在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 00:09:23