Spring Security实现同一浏览器二次登录时强制登出首次会话
解决Google OAuth登录时强制踢掉旧会话的问题
核心问题梳理
同一浏览器下用户未登出时二次登录,需强制失效首次会话(清除服务器端HttpSession及客户端关联Cookie),当前二次登录仅生成新JSESSIONID,但旧会话的Cookie和用户属性仍留存。
具体修改方案
1. 配置SessionRegistry跟踪用户会话
Spring Security需要通过SessionRegistry定位用户的所有活跃会话,先添加Bean:
@Bean public SessionRegistry sessionRegistry() { return new SessionRegistryImpl(); }
2. 完善会话并发控制逻辑
更新sessionManagement配置,启用「新登录踢旧会话」规则,并关联会话注册表:
.sessionManagement((sessions) -> sessions .sessionConcurrency((concurrency) -> concurrency .maximumSessions(1) // 限制单用户仅1个活跃会话 .maxSessionsPreventsLogin(false) // 允许新登录,自动标记旧会话失效(默认值,显式声明更清晰) .sessionRegistry(sessionRegistry()) // 绑定会话注册表 .expiredSessionStrategy(new SessionInformationExpiredStrategy() { @Override public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException { // 旧会话失效时清除所有认证相关Cookie并重定向 HttpServletResponse response = event.getResponse(); String[] cookieNames = {"JSESSIONID", "roles", "username"}; for (String name : cookieNames) { Cookie cookie = new Cookie(name, null); cookie.setPath("/"); cookie.setMaxAge(0); response.addCookie(cookie); } response.sendRedirect("/login.html?sessionExpired=true"); } }) ))
3. 增强OAuth登录成功处理器(可选)
如果默认并发控制未完全生效,可在自定义登录成功处理器中主动遍历并失效旧会话:
@Component public class CustomOauth2SuccessHandler implements AuthenticationSuccessHandler { private final SessionRegistry sessionRegistry; public CustomOauth2SuccessHandler(SessionRegistry sessionRegistry) { this.sessionRegistry = sessionRegistry; } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { // 获取当前登录用户标识(适配Google OAuth返回的用户信息) String username = ""; Object principal = authentication.getPrincipal(); if (principal instanceof OidcUser) { username = ((OidcUser) principal).getPreferredUsername(); } else if (principal instanceof OAuth2User) { username = ((OAuth2User) principal).getAttribute("email"); } // 获取该用户的所有活跃会话 List<SessionInformation> userSessions = sessionRegistry.getAllSessions(username, false); if (!userSessions.isEmpty()) { for (SessionInformation sessionInfo : userSessions) { // 跳过当前新创建的会话 if (!sessionInfo.getSessionId().equals(request.getSession().getId())) { // 强制失效旧会话 sessionInfo.expireNow(); // 清除旧会话关联的Cookie clearAuthCookies(response); } } } // 原有登录成功重定向逻辑 response.sendRedirect("/index.html"); } private void clearAuthCookies(HttpServletResponse response) { String[] cookieNames = {"JSESSIONID", "roles", "username"}; for (String name : cookieNames) { Cookie cookie = new Cookie(name, null); cookie.setPath("/"); cookie.setMaxAge(0); response.addCookie(cookie); } } }
关键说明
SessionRegistry是会话跟踪的核心组件,没有它Spring Security无法定位用户的多会话实例。maxSessionsPreventsLogin(false)是实现「踢旧会话」的关键,设为true会直接阻止新登录,不符合需求。- 旧会话失效后服务器端
HttpSession会自动销毁,但客户端Cookie需手动清除,避免用户用旧Cookie发起无效请求。
内容的提问来源于stack exchange,提问作者shirdisai
相关产品推荐
相关产品推荐

