處理登出

在使用者可以登入的應用程式中,他們也應該能夠登出。

預設情況下,Spring Security 會建立一個 /logout 端點,因此不需要額外的程式碼。

本節的其餘部分涵蓋了一些您需要考慮的使用案例

了解登出的架構

當您包含 spring-boot-starter-security 相依性或使用 @EnableWebSecurity 註解時,Spring Security 將會新增其登出支援,並且預設會回應 GET /logoutPOST /logout

如果您請求 GET /logout,Spring Security 會顯示登出確認頁面。除了為使用者提供有價值的雙重檢查機制外,它還提供了一種簡單的方法來為 POST /logout 提供所需的 CSRF 令牌。

請注意,如果在設定中停用了 CSRF 保護,則不會向使用者顯示登出確認頁面,並且登出會直接執行。

在您的應用程式中,不需要使用 GET /logout 來執行登出。只要請求中存在所需的 CSRF 令牌,您的應用程式就可以簡單地 POST /logout 來觸發登出。

如果您請求 POST /logout,它將使用一系列的 LogoutHandler 執行以下預設操作

完成後,它將執行其預設的 LogoutSuccessHandler,該處理器會重定向到 /login?logout

自訂登出 URI

由於 LogoutFilter 出現在過濾器鏈中的 AuthorizationFilter 之前,因此預設情況下不需要明確允許 /logout 端點。因此,只有您自己建立的自訂登出端點通常需要 permitAll 設定才能訪問。

例如,如果您只想更改 Spring Security 正在比對的 URI,您可以使用以下方式在登出 DSL 中進行更改

自訂登出 URI
  • 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 標頭,如下所示

使用 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,如下所示

使用 Clear-Site-Data 清除 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 中用於自訂登出成功操作的組件。

例如,您可能只想返回狀態碼,而不是重定向。在這種情況下,您可以提供一個成功處理器實例,如下所示

使用 Clear-Site-Data 清除 Cookie
  • 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"
}

這樣將根據需要清除 SecurityContextHolderStrategySecurityContextRepository

此外,您還需要明確允許該端點

未能呼叫 SecurityContextLogoutHandler 意味著 SecurityContext 可能仍然在後續請求中可用,這意味著使用者實際上並未登出。

測試登出

一旦您設定了登出,您就可以使用 Spring Security 的 MockMvc 支援來測試它。

更多與登出相關的參考資料