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

Spring Boot WebSocket(Jdbc+Postgres)超时会话清理及重连异常问题

基于STOMP的Spring Boot WebSocket会话无法正确删除问题

我在基于STOMP协议的Spring Boot WebSocket服务中遇到会话无法正确删除的问题:当客户端关闭或长时间不活动时,即便使用Spring Session(基于PostgreSQL存储)也无法解决。
客户端为Spring Boot STOMP WebSocket客户端,通过Basic认证建立HTTP会话,配置了单用户仅允许一个活跃连接。

相关配置

安全配置

@Configuration
public class SecurityConfiguration {

  @Bean
  public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

    http.authorizeHttpRequests((authz) -> authz.anyRequest().authenticated())
        .httpBasic(withDefaults());

    http.sessionManagement(session -> session
            // 单用户仅允许1个有效会话
            .maximumSessions(1)
            // 尝试多会话登录时直接拒绝
            .maxSessionsPreventsLogin(true)
    );

    return http.build();
  }

  @Value("${spring.datasource.driver-class-name}")
  private String driverclassname;

  @Value("${spring.datasource.username}")
  private String dataSourceUsername;

  @Value("${spring.datasource.password}")
  private String dataSourcePassword;

  @Value("${spring.datasource.url}")
  private String dataSourceUrl;

  @Bean
  public DriverManagerDataSource dataSource() {
    DriverManagerDataSource dataSource = new DriverManagerDataSource();
    dataSource.setDriverClassName(driverclassname);
    dataSource.setUrl(dataSourceUrl);
    dataSource.setUsername(dataSourceUsername);
    dataSource.setPassword(dataSourcePassword);
    return dataSource;
  }

  @Bean
  public UserDetailsManager users(DataSource dataSource) throws SQLException {
    JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource);
    return users;
  }

}

Spring Session配置(application.properties)

server.servlet.session.timeout=1m
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=username
spring.datasource.password=password
spring.datasource.driver-class-name=org.postgresql.Driver
spring.session.store-type=jdbc
spring.session.jdbc.initialize-schema=always
spring.session.jdbc.schema=classpath:org/springframework/session/jdbc/schema-@@platform@@.sql
spring.session.jdbc.table-name=SPRING_SESSION

WebSocket配置

@Configuration
@EnableScheduling
@EnableWebSocketMessageBroker
public class EngineConfig extends AbstractSessionWebSocketMessageBrokerConfigurer<Session> {
  @Override
  public void configureWebSocketTransport(WebSocketTransportRegistration registry) {
    registry.setMessageSizeLimit(WebSockProtocol.MESSAGE_SIZE_LIMIT);
    registry.setSendBufferSizeLimit(WebSockProtocol.SEND_BUFFER_SIZE_LIMIT);
    registry.setSendTimeLimit(WebSockProtocol.SEND_TIME_LIMIT);
  }

  @Override
  public void configureStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint(WebSockProtocol.SRV_ENDPOINT);
  }

  @Override
  public void configureMessageBroker(MessageBrokerRegistry config) {
    config.enableSimpleBroker(WebSockProtocol.MSG_BROKER_PREFIX);
    config.setApplicationDestinationPrefixes(WebSockProtocol.APP_DESTINATION_PREFIX);
  }

  @Bean
  public HttpSessionEventPublisher httpSessionEventPublisher() {
    return new HttpSessionEventPublisher();
  }
}

测试场景与疑问

测试场景:客户端连接后,PostgreSQL中生成会话;关闭客户端2分钟后数据库会话被删除,但同一用户无法再次连接。

请问我哪里配置有误?应如何修改?是否需要改用Redis存储Spring Session?


解决方案分析

核心问题

  1. SessionRegistry与数据库会话不同步:Spring Security的maximumSessions(1)依赖SessionRegistry跟踪活跃会话,虽然数据库中的Spring Session记录已被删除,但SessionRegistry中的会话信息未同步更新,系统仍认为用户存在活跃会话,拒绝新连接。
  2. WebSocket断开未触发HTTP会话销毁:客户端关闭WebSocket连接时,不会立刻触发HTTP会话的销毁事件,容器可能延迟回收会话,导致HttpSessionEventPublisher无法及时感知会话状态变化。

具体修改方案

1. 配置SessionRegistry并关联会话管理

在安全配置中添加SessionRegistry Bean,确保会话注册表能跟踪并更新会话状态:

@Bean
public SessionRegistry sessionRegistry() {
    return new SessionRegistryImpl();
}

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests((authz) -> authz.anyRequest().authenticated())
        .httpBasic(withDefaults());

    http.sessionManagement(session -> session
            .maximumSessions(1)
            .maxSessionsPreventsLogin(true)
            // 关联SessionRegistry,同步会话状态
            .sessionRegistry(sessionRegistry())
    );

    return http.build();
}

2. 添加WebSocket断开事件监听器

监听WebSocket断开事件,主动清理SessionRegistry中的无效会话:

@Component
public class WebSocketDisconnectListener implements ApplicationListener<SessionDisconnectEvent> {

    @Autowired
    private SessionRegistry sessionRegistry;

    @Override
    public void onApplicationEvent(SessionDisconnectEvent event) {
        String sessionId = event.getSessionId();
        SessionInformation sessionInfo = sessionRegistry.getSessionInformation(sessionId);
        if (sessionInfo != null) {
            // 标记会话过期并从注册表移除
            sessionInfo.expireNow();
            sessionRegistry.removeSessionInformation(sessionId);
        }
    }
}

3. 调整Spring Session清理间隔

显式配置Spring Session JDBC的清理定时任务,确保过期会话及时被清理:

# 会话清理间隔,单位毫秒(默认60000)
spring.session.jdbc.cleanup-interval=60000

4. 无需改用Redis存储

PostgreSQL作为Spring Session存储完全可行,问题根源在会话状态同步机制,而非存储介质。完成上述配置后,即可正常使用PostgreSQL存储会话。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 20:34:57