处理登出
本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。 |
在一个终端用户可以 登录 的应用程序中,他们也应该能够注销。
默认情况下,Spring Security 会建立一个 /logout
端点,所以不需要额外的代码。
本节的其余部分涵盖了一些供你考虑的用例:
-
我想 了解logout的架构。
-
我想 自定义注销或注销成功的URI。
-
我想知道什么时候我需要 明确地允许
/logout
端点。 -
我想在用户注销时 清除cookies、storage 和/或缓存。
-
我正在使用 OAuth 2.0,我想 与授权服务器协调注销。
-
我正在使用SAML 2.0,我想 与身份提供者(Identity Provider)协调注销。
-
我正在使用CAS,我想 与身份提供者(Identity Provider)协调注销。
了解 Logout 的架构
当你包含 spring-boot-starter-security
依赖或使用 @EnableWebSecurity
注解时,Spring Security 将添加其注销支持,并默认响应 GET /logout
和 POST /logout
。
如果你请求 GET /logout
,那么 Spring Security 会显示一个注销确认页面。除了为用户提供一个有价值的双重检查机制外,它还提供了一个简单的方法来为 POST /logout
提供 所需的 CSRF token。
在你的应用程序中,没有必要使用 GET /logout 来执行注销。只要请求中存在 所需的 CSRF token,你的应用程序就可以简单地 POST /logout 来诱导注销。
|
如果你请求 POST /logout
,那么它将使用一系列 LogoutHandler
执行以下默认操作:
-
使 HTTP session无效 (
SecurityContextLogoutHandler
)。 -
清理
SecurityContextHolderStrategy
(SecurityContextLogoutHandler
)。 -
清理
SecurityContextRepository
(SecurityContextLogoutHandler
)。 -
清理任何 RememberMe 认证 (
TokenRememberMeServices
/PersistentTokenRememberMeServices
)。 -
清除任何已保存的 CSRF token (
CsrfLogoutHandler
)。 -
触发
LogoutSuccessEvent
(LogoutSuccessEventPublishingLogoutHandler
)
一旦完成,那么它将行使其默认的 LogoutSuccessHandler
,它重定向到 /login?logout
。
自定义注销 URI
由于 LogoutFilter
在 filter chain 中出现在 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 配置,你可以像这样在 logout 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 提供一个自定义接口。
|
一些注销 handler 的配置很常见,以至于它们直接暴露在 logout
DSL 和 <logout>
元素中。一个例子是配置 session 无效,另一个例子是哪些额外的 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 在失效 session 时会将其删除。
|
使用 Clear-Site-Data 来注销用户
Clear-Site-Data
HTTP header 是浏览器支持的一个指令,用于清除属于自己网站的cookie、storage和缓存。这是一个方便和安全的方法,以确保所有的东西,包括会话cookie,在注销时都被清理掉了。
你可以像这样添加配置 Spring Security,以便在注销时写入 Clear-Site-Data
header:
-
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(Directives.COOKIES));
http
.logout((logout) -> logout.addLogoutHandler(clearSiteData))
val clearSiteData = HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter(Directives.COOKIES))
http {
logout {
addLogoutHandler(clearSiteData)
}
}
自定义注销成功
虽然在大多数情况下使用 logoutSuccessUrl
就足够了,但你可能需要在注销完成后做一些不同于重定向到一个URL的事情。 LogoutSuccessHandler
是用于定制注销成功动作的 Spring Security 组件。
例如,你可能希望只返回一个状态代码,而不是重定向。在这种情况下,你可以提供一个 success handler 实例,像这样:
-
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"
}
这样会根据需要清除 SecurityContextHolderStrategy
和 SecurityContextRepository
。
此外,你还需要 明确地许可该端点。
未能调用 SecurityContextLogoutHandler 意味着 SecurityContext 可能在后续请求中仍然可用,这意味着用户实际上没有被注销。
|
测试注销
一旦你配置了注销,你就可以使用 Spring Security 的 MockMvc 支持 来测试它。