处理注销
在最终用户可以 登录 的应用程序中,他们还应该能够注销。
默认情况下,Spring Security 会建立一个 /logout
端点,因此无需其他代码。
本节的其余部分涵盖了您需要考虑的许多用例
-
我想 了解注销的架构
-
我想知道何时需要 明确允许
/logout
端点 -
我想在用户注销时 清除 cookie、存储和/或缓存
-
我正在使用 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
) -
清除任何记住我身份验证 (
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());
http
.logout((logout) -> logout.addLogoutHandler(clearSiteData))
val clearSiteData = HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter())
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.doLogout(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.doLogout(request, response, authentication)
return "redirect:/home"
}
此外,您还需要明确允许该端点。
未能调用 SecurityContextLogoutHandler 意味着 SecurityContext 仍可以在后续请求中使用,这意味着用户实际上并未注销。
|
测试注销
配置注销后,可以使用 Spring Security 的 MockMvc 支持 对其进行测试。