處理登出

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

預設情況下,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,可以在 logout 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 配置,您可以透過在 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 頭,如下所示

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

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

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

自定義登出成功以返回 HTTP 狀態碼
  • 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"
}

這樣會根據需要清除 SecurityContextHolderStrategySecurityContextRepository

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

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

測試登出

配置好登出後,您可以使用Spring Security 的 MockMvc 支援進行測試。

更多登出相關參考