Spring中如何在用户重复登录时立即销毁旧HTTP会话?
解决Spring Boot单登录下WebSocket场景旧会话即时失效的问题
嘿,我完全懂你的困扰——REST接口的单登录配置跑起来没问题,但到了WebSocket场景就掉链子,旧HTTP会话非得等下一次REST请求才会失效,导致前端视图没法及时更新,临时用AuthenticationSuccessEvent监听的方案又总觉得不够优雅。下面给你几个更靠谱的解决方案:
方案一:优化登录成功事件监听,主动清理旧会话+触发WebSocket断开
你当前的临时方案方向是对的,我们可以把它优化得更彻底,确保旧会话被立即销毁,同时主动关闭关联的WebSocket连接:
- 完善
AuthenticationSuccessEvent监听器,不仅终止旧会话,还要主动清理会话注册表信息:
@Component public class AuthenticationSuccessListener implements ApplicationListener<AuthenticationSuccessEvent> { @Autowired private SessionRegistry sessionRegistry; // 假设你维护了一个存储WebSocket会话的容器,key是HTTP会话ID @Autowired private ConcurrentHashMap<String, List<WebSocketSession>> webSocketSessionMap; @Override public void onApplicationEvent(AuthenticationSuccessEvent event) { Authentication auth = event.getAuthentication(); // 获取该用户所有已注册的有效会话 List<SessionInformation> sessions = sessionRegistry.getAllSessions(auth.getPrincipal(), false); if (sessions != null && !sessions.isEmpty()) { for (SessionInformation session : sessions) { // 立即标记旧会话为失效 session.expireNow(); // 从注册表中移除会话信息 sessionRegistry.removeSessionInformation(session.getSessionId()); // 主动关闭关联的WebSocket会话 closeLinkedWebSocketSessions(session.getSessionId()); } } } // 根据HTTP会话ID关闭对应的WebSocket会话 private void closeLinkedWebSocketSessions(String httpSessionId) { List<WebSocketSession> sessions = webSocketSessionMap.remove(httpSessionId); if (sessions != null) { for (WebSocketSession session : sessions) { if (session.isOpen()) { try { session.close(new CloseStatus(CloseStatus.SESSION_NOT_RELIABLE.getCode(), "账号在其他设备登录,会话已失效")); } catch (IOException e) { // 处理关闭异常 } } } } } }
- 在WebSocket握手时绑定HTTP会话ID,方便后续关联查找:
@Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { // 从握手Cookie中提取JSESSIONID String cookieHeader = session.getHandshakeHeaders().getFirst("Cookie"); String httpSessionId = cookieHeader.split("JSESSIONID=")[1].split(";")[0]; // 存入WebSocket会话属性 session.getAttributes().put("HTTP_SESSION_ID", httpSessionId); // 将WebSocket会话存入容器 webSocketSessionMap.computeIfAbsent(httpSessionId, k -> new ArrayList<>()).add(session); }
方案二:替换默认认证策略,实现登录时即时失效旧会话
Spring Security默认的ConcurrentSessionControlAuthenticationStrategy是懒处理的——只有旧会话发起新请求时才会触发失效。我们可以自定义一个策略,让它在新用户登录成功时就立刻干掉旧会话:
- 自定义会话控制策略:
public class ImmediateConcurrentSessionStrategy extends ConcurrentSessionControlAuthenticationStrategy { public ImmediateConcurrentSessionStrategy(SessionRegistry sessionRegistry) { super(sessionRegistry); } @Override protected void allowableSessionsExceeded(List<SessionInformation> sessions, int allowableSessions, SessionRegistry registry) throws SessionAuthenticationException { // 遍历所有旧会话,立即标记失效并清理注册表 for (SessionInformation session : sessions) { session.expireNow(); registry.removeSessionInformation(session.getSessionId()); } // 调用父类逻辑(可选,根据业务需求调整) super.allowableSessionsExceeded(sessions, allowableSessions, registry); } }
- 在Security配置中替换默认策略:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private SessionRegistry sessionRegistry; @Override protected void configure(HttpSecurity http) throws Exception { http .sessionManagement() .maximumSessions(1) .maxSessionsPreventsLogin(false) .sessionRegistry(sessionRegistry) // 替换为自定义的即时失效策略 .sessionAuthenticationStrategy(immediateSessionStrategy()); } @Bean public SessionAuthenticationStrategy immediateSessionStrategy() { ImmediateConcurrentSessionStrategy strategy = new ImmediateConcurrentSessionStrategy(sessionRegistry); strategy.setMaximumSessions(1); return strategy; } @Bean public SessionRegistry sessionRegistry() { return new SessionRegistryImpl(); } }
额外保障:监听会话销毁事件,同步关闭WebSocket
为了确保不管会话是通过哪种方式失效的,都能同步关闭对应的WebSocket连接,我们可以监听SessionDestroyedEvent:
@Component public class SessionDestroyedListener implements ApplicationListener<SessionDestroyedEvent> { @Autowired private ConcurrentHashMap<String, List<WebSocketSession>> webSocketSessionMap; @Override public void onApplicationEvent(SessionDestroyedEvent event) { String sessionId = event.getSessionId(); List<WebSocketSession> sessions = webSocketSessionMap.remove(sessionId); if (sessions != null) { for (WebSocketSession session : sessions) { if (session.isOpen()) { try { session.close(new CloseStatus(CloseStatus.SESSION_NOT_RELIABLE.getCode(), "HTTP会话已失效")); } catch (IOException e) { // 处理异常 } } } } } }
这样一套配置下来,新用户登录时旧HTTP会话会被立即失效,对应的WebSocket连接也会主动关闭,前端视图就能及时同步状态了。
内容的提问来源于stack exchange,提问作者amp-sys
相关产品推荐
相关产品推荐

