處理登出
在使用者可以登入的應用程式中,他們也應該能夠登出。
預設情況下,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 session 無效 (
SecurityContextLogoutHandler
) -
清除
SecurityContextHolderStrategy
(SecurityContextLogoutHandler
) -
清除任何「記住我」身份驗證 (
TokenRememberMeServices
/PersistentTokenRememberMeServices
) -
清除任何已儲存的 CSRF 令牌 (
CsrfLogoutHandler
) -
觸發
LogoutSuccessEvent
(LogoutSuccessEventPublishingLogoutHandler
)
完成後,它將執行其預設的 LogoutSuccessHandler
,該處理器會重定向到 /login?logout
。
自訂登出 URI
由於 LogoutFilter
出現在過濾器鏈中的 AuthorizationFilter
之前,因此預設情況下不需要明確允許 /logout
端點。因此,只有您自己建立的自訂登出端點通常需要 permitAll
設定才能訪問。
例如,如果您只想更改 Spring Security 正在比對的 URI,您可以使用以下方式在登出 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 設定,您可以通過在登出 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 提供。 |
一些登出處理器設定非常常見,以至於它們直接在登出 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 標頭是瀏覽器支援的一種指令,用於清除屬於擁有網站的 Cookie、儲存空間和快取。這是一種方便且安全的方式,可確保在登出時清除所有內容,包括 session 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 提供。 |
建立自訂登出端點
強烈建議您使用提供的登出 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 支援來測試它。