跳到主要内容

处理登出

QWen Max 中英对照 Logout Handling Logouts

在终端用户可以登录的应用程序中,他们也应该能够注销。

默认情况下,Spring Security 会创建一个 /logout 端点,因此不需要额外的代码。

本节其余部分涵盖了许多你可以考虑的用例:

了解 Logout 的架构

当你包含 spring-boot-starter-security 依赖 或使用 @EnableWebSecurity 注解时,Spring Security 将添加其注销支持,并默认响应 GET /logoutPOST /logout

如果你请求 GET /logout,那么 Spring Security 会显示一个注销确认页面。除了为用户提供一个有价值的双重检查机制外,它还提供了一种简单的方法来为 POST /logout 提供所需的 CSRF 令牌

请注意,如果在配置中禁用了CSRF 保护,则不会向用户显示注销确认页面,并且会直接执行注销操作。

提示

在你的应用程序中,不需要使用 GET /logout 来执行登出操作。只要请求中包含所需的 CSRF 令牌,你的应用程序可以直接 POST /logout 来触发登出。

如果你请求 POST /logout,那么它将使用一系列 LogoutHandler 实例执行以下默认操作:

一旦完成,它将执行其默认的 LogoutSuccessHandler,该处理器会重定向到 /login?logout

自定义注销 URI

由于 LogoutFilter 出现在 过滤器链 中的 AuthorizationFilter 之前,因此默认情况下不必显式允许 /logout 端点。因此,通常只有你自己创建的 自定义注销端点 需要一个 permitAll 配置才能被访问。

例如,如果你想简单地更改 Spring Security 匹配的 URI,你可以在 logout DSL 中以如下方式实现:

http
.logout((logout) -> logout.logoutUrl("/my/logout/uri"))
java

并且不需要进行授权更改,因为它只是调整了 LogoutFilter

但是,如果你自己设置了一个登出成功端点(或者在极少数情况下,你自己的登出端点),比如说使用 Spring MVC,你需要在 Spring Security 中允许它。这是因为 Spring MVC 在 Spring Security 之后处理你的请求。

你可以使用 authorizeHttpRequests<intercept-url> 来实现这一点,如下所示:

http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/my/success/endpoint").permitAll()
// ...
)
.logout((logout) -> logout.logoutSuccessUrl("/my/success/endpoint"))
java

在此示例中,你告诉 LogoutFilter 在完成后重定向到 /my/success/endpoint。并且,你在授权过滤器中明确允许了 /my/success/endpoint 端点。

不过,指定两次可能会很麻烦。如果你使用的是 Java 配置,可以在登出 DSL 中设置 permitAll 属性,如下所示:

http
.authorizeHttpRequests((authorize) -> authorize
// ...
)
.logout((logout) -> logout
.logoutSuccessUrl("/my/success/endpoint")
.permitAll()
)
java

这将为您把所有登出 URI 添加到允许列表中。

添加清理操作

如果你使用的是 Java 配置,可以通过在 logout DSL 中调用 addLogoutHandler 方法来添加你自己的清理操作,如下所示:

CookieClearingLogoutHandler cookies = new CookieClearingLogoutHandler("our-custom-cookie");
http
.logout((logout) -> logout.addLogoutHandler(cookies))
java
备注

因为 LogoutHandler 实例是为了清理目的,所以它们不应该抛出异常。

提示

由于 LogoutHandler 是一个函数式接口,你可以用 lambda 表达式提供一个自定义的实现。

一些注销处理器配置非常常见,因此它们直接在 logout DSL 和 <logout> 元素中公开。一个例子是配置会话失效,另一个是要删除哪些额外的 cookie。

例如,你可以配置 CookieClearingLogoutHandler,如上面所示。

或者你可以像这样设置相应的配置值:

http
.logout((logout) -> logout.deleteCookies("our-custom-cookie"))
java
备注

指定 JSESSIONID cookie 是不必要的,因为 SecurityContextLogoutHandler 通过使会话失效来移除它。

使用 Clear-Site-Data 注销用户

Clear-Site-Data HTTP 头是浏览器支持的一种指令,用于清除属于所属网站的 cookies、存储和缓存。这是一种方便且安全的方法,可以确保在登出时包括会话 cookie 在内的所有内容都被清理干净。

你可以配置 Spring Security 以在注销时写入 Clear-Site-Data 头,如下所示:

HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter());
http
.logout((logout) -> logout.addLogoutHandler(clearSiteData))
java

你将想要清除的项目列表传递给 ClearSiteDataHeaderWriter 构造函数。

上述配置会清除所有站点数据,但你也可以配置它仅删除 cookies,如下所示:

HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(Directive.COOKIES));
http
.logout((logout) -> logout.addLogoutHandler(clearSiteData))
java

自定义注销成功

在大多数情况下,使用 logoutSuccessUrl 就足够了,但在注销完成后,你可能需要执行不同于重定向到 URL 的其他操作。LogoutSuccessHandler 是 Spring Security 用于自定义注销成功操作的组件。

例如,你可能不想重定向,而只是返回一个状态码。这种情况下,你可以提供一个成功的处理实例,如下所示:

http
.logout((logout) -> logout.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()))
java
提示

由于 LogoutSuccessHandler 是一个函数式接口,你可以使用 lambda 表达式提供一个自定义的实现。

创建自定义登出端点

强烈建议您使用提供的 logout DSL 来配置登出。原因之一是很容易忘记调用所需的 Spring Security 组件,以确保正确且完整的登出。

事实上,通常比创建一个 Spring MVC 端点来执行注销操作更简单的是注册一个自定义的 LogoutHandler

也就是说,如果你发现自己处于需要自定义登出端点的情况,比如下面这个:

@PostMapping("/my/logout")
public String performLogout() {
// .. perform logout
return "redirect:/home";
}
java

那么你需要让该端点调用 Spring Security 的 SecurityContextLogoutHandler 以确保安全和完整的退出。至少需要如下内容:

SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();

@PostMapping("/my/logout")
public String performLogout(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
// .. perform logout
this.logoutHandler.doLogout(request, response, authentication);
return "redirect:/home";
}
java

另外,你还需要显式允许该端点

注意

未调用 SecurityContextLogoutHandler 意味着 the SecurityContext 在后续请求中仍然可用,这意味着用户实际上并未登出。

测试登出

一旦你配置好了登出,可以使用Spring Security 的 MockMvc 支持来测试它。

更多关于登出的相关参考