1. 简介

Spring Security 是保护 Spring 应用的事实标准,提供了完整的认证与授权机制,涵盖登录、登出、权限控制等核心功能。

本文聚焦于 如何在 Spring Security 中实现手动登出(Manual Logout),并深入几种常见的登出场景处理方式。

⚠️ 前提:假设你已经熟悉 Spring Security 的默认登出流程,本文不再赘述基础配置。

2. 基础登出

当用户触发登出操作时,系统需要清理当前会话状态,核心包括两个动作:

✅ 使 HTTP Session 失效
✅ 清除 SecurityContext(其中保存了用户的认证信息)

Spring Security 提供了 SecurityContextLogoutHandler 来完成上述两个步骤。

来看一个基础配置示例:

@Configuration
public class DefaultLogoutConfiguration {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
          .logout(logout -> logout
            .logoutUrl("/basic/basiclogout")
            .addLogoutHandler(new SecurityContextLogoutHandler())
          );
        return http.build();
    }
}

📌 注意:SecurityContextLogoutHandler 实际上是 Spring Security 默认自动添加的,这里显式写出是为了让流程更清晰,便于理解。

在实际项目中,登出往往还需要清除用户的某些 Cookie(比如 remember-me、tracking ID 等)。

我们可以通过自定义 LogoutHandler,在登出时遍历并清除所有 Cookie:

@Configuration
public class AllCookieClearingLogoutConfiguration {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
          .logout(logout -> logout
            .logoutUrl("/cookies/cookielogout")
            .addLogoutHandler((request, response, auth) -> {
                for (Cookie cookie : request.getCookies()) {
                    String cookieName = cookie.getName();
                    Cookie cookieToDelete = new Cookie(cookieName, null);
                    cookieToDelete.setMaxAge(0); // 立即过期
                    response.addCookie(cookieToDelete);
                }
            })
          );
        return http.build();
    }
}

💡 小技巧:Spring Security 其实已经内置了 CookieClearingLogoutHandler,可直接使用,无需手写循环逻辑。例如:

.addLogoutHandler(new CookieClearingLogoutHandler("JSESSIONID", "remember-me"))

这样更简洁,也更安全。

4. 使用 Clear-Site-Data Header 登出

除了操作 Cookie,现代浏览器支持通过 HTTP 响应头 Clear-Site-Data 来批量清除站点数据(如 Cookie、缓存、本地存储等)。

这个机制特别适合 SPA 或前后端分离架构,能一站式清理客户端残留状态。

配置方式如下:

@Configuration
public class ClearSiteDataHeaderLogoutConfiguration {

    private static final ClearSiteDataHeaderWriter.Directive[] SOURCE = 
      {CACHE, COOKIES, STORAGE, EXECUTION_CONTEXTS};

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
          .logout(logout -> logout
            .logoutUrl("/csd/csdlogout")
            .addLogoutHandler(new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(SOURCE)))
          );
        return http.build();
    }
}

⚠️ 注意事项:

  • Clear-Site-Data 是浏览器行为,依赖客户端支持(现代主流浏览器基本都支持)
  • 出于安全考虑,该 Header 仅在 HTTPS 请求下生效,否则会被忽略
  • 如果只清除部分数据(如只清 Cookie 不清 Storage),可能造成状态不一致(Incomplete Clearing),建议全量清除或按需明确指定

5. 基于 HttpServletRequest.logout() 的登出

Servlet 3.1+ 提供了标准 API:HttpServletRequest.logout(),Spring Security 也对其做了集成支持。

我们可以在 LogoutHandler 中调用该方法,由容器统一处理登出逻辑。

配置示例:

@Configuration
public static class LogoutOnRequestConfiguration {
    private static final Logger logger = LoggerFactory.getLogger(LogoutOnRequestConfiguration.class);

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.securityMatchers("/request/**")
            .authorizeHttpRequests(authz -> authz.anyRequest()
                .permitAll())
            .logout(logout -> logout.logoutUrl("/request/logout")
                .addLogoutHandler((request, response, auth) -> {
                    try {
                        request.logout();
                    } catch (ServletException e) {
                        logger.error(e.getMessage());
                    }
                }));
        return http.build();
    }
}

✅ 测试用例验证登出是否生效:

@Test
public void givenLoggedUserWhenUserLogoutOnRequestThenSessionCleared() throws Exception {

    this.mockMvc.perform(post("/request/logout").secure(true)
        .with(csrf()))
        .andExpect(status().is3xxRedirection())
        .andExpect(unauthenticated())
        .andReturn();
}

📌 说明:

  • .secure(true) 模拟 HTTPS 请求,避免 Clear-Site-Data 等机制被跳过
  • unauthenticated() 断言用户已登出,SecurityContext 已清空

6. 总结

Spring Security 提供了灵活且强大的登出机制,支持多种场景组合:

SecurityContextLogoutHandler —— 清认证上下文(默认必选)
CookieClearingLogoutHandler —— 清 Cookie(适合 remember-me 场景)
Clear-Site-Data Header —— 客户端全量清理(推荐用于前后端分离)
HttpServletRequest.logout() —— 调用容器原生登出(兼容性好)

在实际项目中,可以根据需求组合多个 LogoutHandler,实现干净、彻底的用户登出体验。

示例代码已托管至 GitHub:https://github.com/eugenp/tutorials/tree/master/spring-security-modules/spring-security-web-login-2


原始标题:Manual Logout With Spring Security