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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 07:02:47