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

集成Spring Session至WebSocket实现Http Session清理及问题排查

Spring Boot WebSocket会话管理与认证问题排查

场景与需求

在Heroku部署Spring Boot WebSocket服务器,需实现以下功能:

  • HTTP基础认证(基于PostgreSQL数据库)
  • 单用户仅允许一个活跃会话
  • 用户断开连接时自动清理/失效已认证会话,避免无法重连

现有Security配置

当前SecurityFilterChain配置如下:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests((authz) -> authz.anyRequest().authenticated())
        .httpBasic(withDefaults())
        .sessionManagement(session -> session
            .maximumSessions(1)
            .maxSessionsPreventsLogin(true));
    return http.build();
}

会话超时疑问

尝试设置server.servlet.session.timeout=1m,期望通过WebSocket心跳维持Http Session存活直至用户断开,但1分钟后连接仍断开。已知心跳对Heroku有效:设置server.servlet.session.timeout=10m后,客户端闲置数分钟未被Heroku(默认55秒切断闲置连接)断开。疑问:为何WebSocket心跳无法维持Http Session存活?

Spring Session配置错误及当前配置

为更好控制会话生命周期引入Spring Session,却遇到以下Bean缺失错误:

Field sessionRepository in org.springframework.session.web.socket.config.annotation.AbstractSessionWebSocketMessageBrokerConfigurer required a bean of type 'org.springframework.session.SessionRepository' that could not be found.

The injection point has the following annotations:
    - @org.springframework.beans.factory.annotation.Autowired(required=true)

Action:
Consider defining a bean of type 'org.springframework.session.SessionRepository' in your configuration.

当前WebSocket配置类:

@Configuration
@EnableWebSocketMessageBroker
public class EngineConfig extends AbstractSessionWebSocketMessageBrokerConfigurer<Session> {

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

  @Override
  public void configureWebSocketTransport(WebSocketTransportRegistration registry) {
    registry.setMessageSizeLimit(MESSAGE_SIZE_LIMIT);
    registry.setSendBufferSizeLimit(SEND_BUFFER_SIZE_LIMIT);
    registry.setSendTimeLimit(SEND_TIME_LIMIT);
  }

  @Override
  public void configureMessageBroker(MessageBrokerRegistry config) {
    config.enableSimpleBroker(MSG_BROKER_PREFIX)
        .setHeartbeatValue(new long[]{HEART_BEAT_TIME, HEART_BEAT_SEND})
        .setTaskScheduler(heartBeatScheduler());
    config.setApplicationDestinationPrefixes(APP_DESTINATION_PREFIX);
  }

  @Bean
  public TaskScheduler heartBeatScheduler() {
    return new ConcurrentTaskScheduler();
  }

}

application.properties配置:

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

解决建议

1. WebSocket心跳无法维持Http Session的原因与解决方案

  • 核心差异:WebSocket心跳是WebSocket连接层面的保活机制,不会自动触发Http Session的活跃度刷新。Http Session的超时判定仅基于Http请求(如初始握手、后续Http接口调用),WebSocket帧不属于Http请求范畴,因此即使心跳持续发送,Http Session仍会按设置的超时时间失效。
  • 修复方案:在WebSocket消息处理流程中手动刷新Http Session。例如通过拦截器实现:
@Component
public class SessionRefreshInterceptor implements HandshakeInterceptor {

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        if (request instanceof ServletServerHttpRequest) {
            HttpSession session = ((ServletServerHttpRequest) request).getServletRequest().getSession();
            session.setMaxInactiveInterval((int) TimeUnit.MINUTES.toSeconds(10)); // 重置超时时间
            attributes.put("HTTP_SESSION_ID", session.getId()); // 存入会话属性用于后续清理
        }
        return true;
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
        // 无需操作
    }
}

将拦截器添加到WebSocket端点:

@Override
public void configureStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint(SRV_ENDPOINT).addInterceptors(new SessionRefreshInterceptor());
}

2. SessionRepository Bean缺失问题修复

  • 检查依赖:确保Spring Boot项目引入了必要依赖(Maven示例):
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-core</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-jdbc</artifactId>
</dependency>
  • 替换旧版配置类:AbstractSessionWebSocketMessageBrokerConfigurer是Spring Session旧版API,新版无需手动继承。简化WebSocket配置类:
@Configuration
@EnableWebSocketMessageBroker
public class EngineConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint(SRV_ENDPOINT).addInterceptors(new SessionRefreshInterceptor());
    }

    @Override
    public void configureWebSocketTransport(WebSocketTransportRegistration registry) {
        registry.setMessageSizeLimit(MESSAGE_SIZE_LIMIT);
        registry.setSendBufferSizeLimit(SEND_BUFFER_SIZE_LIMIT);
        registry.setSendTimeLimit(SEND_TIME_LIMIT);
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker(MSG_BROKER_PREFIX)
            .setHeartbeatValue(new long[]{HEART_BEAT_TIME, HEART_BEAT_SEND})
            .setTaskScheduler(heartBeatScheduler());
        config.setApplicationDestinationPrefixes(APP_DESTINATION_PREFIX);
    }

    @Bean
    public TaskScheduler heartBeatScheduler() {
        return new ConcurrentTaskScheduler();
    }
}
  • 手动注册SessionRepository(可选):若Spring Boot自动配置未生效,手动创建Bean:
@Bean
public SessionRepository<org.springframework.session.Session> sessionRepository(JdbcOperations jdbcOperations, PlatformTransactionManager transactionManager) {
    JdbcIndexedSessionRepository repository = new JdbcIndexedSessionRepository(jdbcOperations, transactionManager);
    repository.setTableName("SPRING_SESSION");
    return repository;
}

3. 断开连接时自动清理会话

实现WebSocket断开事件监听器,在连接关闭时主动失效会话:

@Component
public class WebSocketSessionCleanupListener implements ApplicationListener<SessionDisconnectEvent> {

    private final SessionRepository<org.springframework.session.Session> sessionRepository;

    public WebSocketSessionCleanupListener(SessionRepository<org.springframework.session.Session> sessionRepository) {
        this.sessionRepository = sessionRepository;
    }

    @Override
    public void onApplicationEvent(SessionDisconnectEvent event) {
        String sessionId = event.getSessionAttributes().get("HTTP_SESSION_ID").toString();
        org.springframework.session.Session session = sessionRepository.findById(sessionId);
        if (session != null) {
            sessionRepository.deleteById(sessionId);
        }
    }
}

4. 协同Security会话管理与Spring Session

配置SessionRegistry让Security识别Spring Session存储的会话:

@Bean
public SessionRegistry sessionRegistry(SessionRepository<org.springframework.session.Session> sessionRepository) {
    return new SpringSessionBackedSessionRegistry<>(sessionRepository);
}

更新SecurityFilterChain配置:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http, SessionRegistry sessionRegistry) throws Exception {
    http.authorizeHttpRequests((authz) -> authz.anyRequest().authenticated())
        .httpBasic(withDefaults())
        .sessionManagement(session -> session
            .maximumSessions(1)
            .maxSessionsPreventsLogin(true)
            .sessionRegistry(sessionRegistry));
    return http.build();
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 06:57:10