处理注销
在最终用户可以登录的应用程序中,他们也应该能够注销。
默认情况下,Spring Security 会启动一个 /logout 端点,因此无需额外的代码。
本节的其余部分涵盖了一些你需要考虑的用例
-
我想了解注销的架构
-
我想知道何时需要明确允许
/logout端点 -
我想在用户注销时清除 cookies、存储和/或缓存
-
我正在使用 OAuth 2.0,并且我想与授权服务器协调注销
-
我正在使用 SAML 2.0,并且我想与身份提供者协调注销
-
我正在使用 CAS,并且我想与身份提供者协调注销
理解注销的架构
当你包含 spring-boot-starter-security 依赖或使用 @EnableWebSecurity 注解时,Spring Security 将添加其注销支持,并且默认情况下会响应 GET /logout 和 POST /logout。
如果你请求 GET /logout,那么 Spring Security 将显示一个注销确认页面。除了为用户提供有价值的双重检查机制外,它还提供了一种简单的方式来为 POST /logout 提供所需的 CSRF 令牌。
请注意,如果配置中禁用了CSRF 保护,则不会向用户显示注销确认页面,并且将直接执行注销。
在你的应用程序中,无需使用 GET /logout 来执行注销。只要请求中存在所需的 CSRF 令牌,你的应用程序只需 POST /logout 即可引发注销。 |
如果你请求 POST /logout,那么它将使用一系列LogoutHandler 实例执行以下默认操作
-
使 HTTP 会话无效(
SecurityContextLogoutHandler) -
清除
SecurityContextHolderStrategy(SecurityContextLogoutHandler) -
清除任何RememberMe 身份验证(
TokenRememberMeServices/PersistentTokenRememberMeServices) -
清除任何已保存的CSRF 令牌(
CsrfLogoutHandler) -
触发
LogoutSuccessEvent(LogoutSuccessEventPublishingLogoutHandler)
完成后,它将执行其默认的LogoutSuccessHandler,该处理程序将重定向到 /login?logout。
自定义注销 URI
由于 LogoutFilter 出现在过滤器链中的AuthorizationFilter之前,因此默认情况下无需明确允许 /logout 端点。因此,只有你自行创建的自定义注销端点通常需要 permitAll 配置才能访问。
例如,如果你只想更改 Spring Security 匹配的 URI,可以通过以下方式在 logout DSL 中进行更改
-
Java
-
Kotlin
-
Xml
http
.logout((logout) -> logout.logoutUrl("/my/logout/uri"))
http {
logout {
logoutUrl = "/my/logout/uri"
}
}
<logout logout-url="/my/logout/uri"/>
并且不需要进行任何授权更改,因为它只是调整了 LogoutFilter。
但是,如果你设置了自己的注销成功端点(或者在极少数情况下,你自己的注销端点),例如使用Spring MVC,你将需要在 Spring Security 中允许它。这是因为 Spring MVC 在 Spring Security 处理请求之后才处理你的请求。
你可以使用 authorizeHttpRequests 或 <intercept-url> 这样做
-
Java
-
Kotlin
-
Xml
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/my/success/endpoint").permitAll()
// ...
)
.logout((logout) -> logout.logoutSuccessUrl("/my/success/endpoint"))
http {
authorizeHttpRequests {
authorize("/my/success/endpoint", permitAll)
}
logout {
logoutSuccessUrl = "/my/success/endpoint"
}
}
<http>
<filter-url pattern="/my/success/endpoint" access="permitAll"/>
<logout logout-success-url="/my/success/endpoint"/>
</http>
在此示例中,你告诉 LogoutFilter 在完成后重定向到 /my/success/endpoint。并且,你在AuthorizationFilter中明确允许 /my/success/endpoint 端点。
但是,指定两次可能会很麻烦。如果你正在使用 Java 配置,可以改为在注销 DSL 中设置 permitAll 属性,如下所示
-
Java
-
Kotlin
http
.authorizeHttpRequests((authorize) -> authorize
// ...
)
.logout((logout) -> logout
.logoutSuccessUrl("/my/success/endpoint")
.permitAll()
)
http
authorizeHttpRequests {
// ...
}
logout {
logoutSuccessUrl = "/my/success/endpoint"
permitAll = true
}
这将为你将所有注销 URI 添加到允许列表中。
添加清理操作
如果你正在使用 Java 配置,可以通过在 logout DSL 中调用 addLogoutHandler 方法来添加自己的清理操作,如下所示
-
Java
-
Kotlin
CookieClearingLogoutHandler cookies = new CookieClearingLogoutHandler("our-custom-cookie");
http
.logout((logout) -> logout.addLogoutHandler(cookies))
http {
logout {
addLogoutHandler(CookieClearingLogoutHandler("our-custom-cookie"))
}
}
由于LogoutHandler实例用于清理,因此它们不应抛出异常。 |
由于LogoutHandler是一个函数式接口,你可以将其作为 lambda 提供。 |
一些注销处理程序配置非常常见,它们直接在 logout DSL 和 <logout> 元素中公开。一个例子是配置会话失效,另一个是应该删除哪些附加 cookie。
例如,你可以配置上面看到的CookieClearingLogoutHandler。
-
Java
-
Kotlin
-
Xml
http
.logout((logout) -> logout.deleteCookies("our-custom-cookie"))
http {
logout {
deleteCookies("our-custom-cookie")
}
}
<http>
<logout delete-cookies="our-custom-cookie"/>
</http>
指定 JSESSIONID cookie 不是必需的,因为SecurityContextLogoutHandler通过使会话失效来删除它。 |
使用 Clear-Site-Data 注销用户
Clear-Site-Data HTTP 标头是浏览器支持的一种指令,用于清除属于所属网站的 cookie、存储和缓存。这是一种方便且安全的方法,可以确保在注销时清除所有内容,包括会话 cookie。
你可以配置 Spring Security 以便在注销时写入 Clear-Site-Data 标头,如下所示
-
Java
-
Kotlin
HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(Directives.ALL));
http
.logout((logout) -> logout.addLogoutHandler(clearSiteData))
val clearSiteData = HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter(Directives.ALL))
http {
logout {
addLogoutHandler(clearSiteData)
}
}
你为 ClearSiteDataHeaderWriter 构造函数提供了要清除的项目列表。
上述配置清除了所有站点数据,但你也可以将其配置为仅清除 cookie,如下所示
-
Java
-
Kotlin
HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(Directive.COOKIES));
http
.logout((logout) -> logout.addLogoutHandler(clearSiteData))
val clearSiteData = HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter(Directive.COOKIES))
http {
logout {
addLogoutHandler(clearSiteData)
}
}
自定义注销成功
虽然在大多数情况下 logoutSuccessUrl 就足够了,但你可能需要在注销完成后执行与重定向到 URL 不同的操作。LogoutSuccessHandler 是 Spring Security 中用于自定义注销成功操作的组件。
例如,与其重定向,你可能只想返回一个状态码。在这种情况下,你可以提供一个成功处理程序实例,如下所示
-
Java
-
Kotlin
-
Xml
http
.logout((logout) -> logout.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()))
http {
logout {
logoutSuccessHandler = HttpStatusReturningLogoutSuccessHandler()
}
}
<bean name="mySuccessHandlerBean" class="org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler"/>
<http>
<logout success-handler-ref="mySuccessHandlerBean"/>
</http>
由于LogoutSuccessHandler是一个函数式接口,你可以将其作为 lambda 提供。 |
创建自定义注销端点
强烈建议你使用提供的 logout DSL 来配置注销。原因之一是很容易忘记调用所需的 Spring Security 组件以确保正确和完整的注销。
事实上,注册自定义 LogoutHandler 通常比为执行注销创建Spring MVC端点更简单。
也就是说,如果你发现自己需要自定义注销端点,如下所示
-
Java
-
Kotlin
@PostMapping("/my/logout")
public String performLogout() {
// .. perform logout
return "redirect:/home";
}
@PostMapping("/my/logout")
fun performLogout(): String {
// .. perform logout
return "redirect:/home"
}
那么你将需要让该端点调用 Spring Security 的SecurityContextLogoutHandler,以确保安全且完整的注销。至少需要以下内容
-
Java
-
Kotlin
SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
@PostMapping("/my/logout")
public String performLogout(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
// .. perform logout
this.logoutHandler.logout(request, response, authentication);
return "redirect:/home";
}
val logoutHandler = SecurityContextLogoutHandler()
@PostMapping("/my/logout")
fun performLogout(val authentication: Authentication, val request: HttpServletRequest, val response: HttpServletResponse): String {
// .. perform logout
this.logoutHandler.logout(request, response, authentication)
return "redirect:/home"
}
此外,你需要明确允许该端点。
未能调用SecurityContextLogoutHandler意味着SecurityContext可能仍可在后续请求中可用,这意味着用户实际上并未注销。 |
测试注销
配置注销后,你可以使用Spring Security 的 MockMvc 支持对其进行测试。