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

SimpUserRegistry在EC2/Heroku异常:部分用户未加入注册表

解决SimpUserRegistry在线用户数统计异常问题

根据你描述的场景——本地运行正常,但部署到AWS EC2(仅弹性IP无负载均衡)和Heroku时部分用户无法被SimpUserRegistry统计,结合排查到的现象(SockJS CONNECTED响应无user-name头、SessionConnectedEvent用户为null但SessionConnectEvent有正确用户),我来梳理下可能的根因和对应的解决办法:

可能的根因分析

  1. WebSocket会话用户绑定时机不匹配
    你提到SecurityChannelInterceptor完成了所有用户认证,但SessionConnectedEvent中的用户为null。这说明在SessionConnectedEvent触发时,用户信息还没有被正确关联到WebSocket会话上。SessionConnectEvent是在握手阶段触发的,此时拦截器已经设置了用户,但SessionConnectedEvent是在会话完全建立后触发,中间可能存在用户信息没有同步到会话上下文的情况。

  2. 部署环境的WebSocket头信息传递问题

    • Heroku环境本身会通过反向代理处理WebSocket请求,可能默认没有传递某些关键头(比如用户认证相关的头,或者WebSocket协议所需的头),导致会话建立后无法正确提取用户信息。
    • AWS EC2虽然直接暴露,但如果你的应用使用了嵌入式容器(比如Tomcat),容器在公网环境下的WebSocket配置可能存在差异,导致user-name头无法被正确添加到CONNECTED响应中。
  3. SimpUserRegistry的实例一致性问题
    如果你在多个地方注入SimpUserRegistry,有可能出现实例不一致的情况(比如某些地方注入的是代理实例,而非真正的注册表实例),导致统计时读取的是未更新的实例数据。

针对性解决方案

1. 确保用户信息在握手阶段就绑定到会话

放弃仅依赖通道拦截器设置用户,改用WebSocket握手拦截器在握手阶段就将用户信息绑定到会话属性中,这样SessionConnectedEvent触发时就能正确获取用户:

@Component
public class HandshakeAuthInterceptor implements HandshakeInterceptor {

    private final SecurityService securityService;

    public HandshakeAuthInterceptor(SecurityService securityService) {
        this.securityService = securityService;
    }

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        // 替换成你实际的认证逻辑,从请求中获取认证信息
        Authentication auth = securityService.getAuthenticationFromRequest(request);
        if (auth != null && auth.isAuthenticated()) {
            // 将用户信息存入会话属性,Spring WebSocket会自动关联到SimpUserRegistry
            attributes.put(Principal.class.getName(), auth);
        }
        return true;
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {}
}

然后在WebSocketMessageBrokerConfig中注册这个拦截器:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketMessageBrokerConfig implements WebSocketMessageBrokerConfigurer {

    private final HandshakeAuthInterceptor handshakeAuthInterceptor;

    public WebSocketMessageBrokerConfig(HandshakeAuthInterceptor handshakeAuthInterceptor) {
        this.handshakeAuthInterceptor = handshakeAuthInterceptor;
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws")
                .withSockJS()
                .setInterceptors(handshakeAuthInterceptor); // 添加握手拦截器
    }

    // 其他配置...
}

2. 手动处理SessionConnectEvent将用户注册到SimpUserRegistry

既然SessionConnectEvent中已经有正确的用户对象,我们可以监听这个事件,手动将用户注册到注册表中,避免依赖自动关联:

@Component
public class SessionConnectEventListener {

    private final SimpUserRegistry simpUserRegistry;

    public SessionConnectEventListener(SimpUserRegistry simpUserRegistry) {
        this.simpUserRegistry = simpUserRegistry;
    }

    @EventListener
    public void handleSessionConnect(SessionConnectEvent event) {
        Authentication auth = (Authentication) event.getUser();
        if (auth != null && auth.isAuthenticated()) {
            // 手动创建SimpUser并注册
            SimpUser simpUser = new DefaultSimpUser(auth.getName(), auth);
            // 注意:Spring 5+可使用SimpUserRegistry的registerUser方法,若版本不支持可向下转型为DefaultSimpUserRegistry
            ((DefaultSimpUserRegistry) simpUserRegistry).registerUser(simpUser);
        }
    }
}

提示:若担心依赖内部实现类DefaultSimpUserRegistry,也可以通过SimpMessagingTemplate获取会话上下文来关联用户。

3. 适配部署环境的WebSocket配置

  • Heroku环境:确保应用已启用WebSocket支持,在application.properties中添加:
spring.web.socket.enabled=true
server.tomcat.additional-tld-skip-patterns=*.jar

同时配置信任Heroku反向代理传递的头信息,确保WebSocket握手请求的原始信息被正确解析:

@Configuration
public class ServerConfig {
    @Bean
    public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {
        return factory -> factory.addConnectorCustomizers(connector -> {
            connector.setScheme("https");
            connector.setProxyPort(443);
        });
    }
}
  • AWS EC2环境:如果使用嵌入式Tomcat,检查Tomcat的WebSocket配置,比如设置合理的最大会话数、允许的Origin列表等,避免会话建立时出现异常导致用户无法注册。

4. 验证SimpUserRegistry的实例一致性

在所有使用SimpUserRegistry的地方(监听器、控制器、定时任务),打印实例的hashCode(),确认是否为同一个实例。如果出现多个实例,检查配置类是否存在重复创建实例的情况,比如是否同时使用了@Bean和@Autowired导致多实例。

额外调试建议

  • 在SecurityChannelInterceptor中添加日志,打印每个请求的认证结果和用户信息,确认认证确实完成。
  • 在SessionConnectedEvent监听器中打印会话ID和上下文信息,检查是否能获取到用户相关的属性。
  • 查看浏览器的Network面板,检查WebSocket握手请求的响应头,确认是否有user-name头,以及请求头中的认证信息是否正确传递。

内容的提问来源于stack exchange,提问作者Bhesh Gurung

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:36:00