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?
解决方案分析
核心问题
- SessionRegistry与数据库会话不同步:Spring Security的
maximumSessions(1)依赖SessionRegistry跟踪活跃会话,虽然数据库中的Spring Session记录已被删除,但SessionRegistry中的会话信息未同步更新,系统仍认为用户存在活跃会话,拒绝新连接。 - 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
相关产品推荐
相关产品推荐

