處理登出
在終端使用者可以登入的應用程式中,他們也應該能夠登出。
預設情況下,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 頭是瀏覽器支援的一個指令,用於清除屬於網站的 cookies、儲存和快取。這是一個便捷且安全的方式,確保在登出時清理所有內容,包括會話 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
建構函式提供您希望清除的專案列表。
上述配置會清除所有站點資料,但您也可以配置它只刪除 cookies,如下所示
-
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"
}
這樣會根據需要清除 SecurityContextHolderStrategy
和 SecurityContextRepository
。
此外,您還需要明確允許該端點。
未能呼叫SecurityContextLogoutHandler 意味著在後續請求中 SecurityContext 仍然可能可用,這意味著使用者並未真正登出。 |
測試登出
配置好登出後,您可以使用Spring Security 的 MockMvc 支援進行測試。