處理登出
在終端使用者可以登入的應用程式中,他們也應該能夠登出。
預設情況下,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 支援對其進行測試。