身份驗證持久化與 Session 管理

一旦您擁有一個驗證請求的應用程式,重要的是要考慮如何持久化和還原後續請求產生的身份驗證。

預設情況下,這是自動完成的,因此不需要額外的程式碼,但重要的是要知道 HttpSecurity 中的 requireExplicitSave 代表什麼意思。

如果您願意,您可以閱讀更多關於 requireExplicitSave 的作用為什麼它很重要。否則,在大多數情況下,您已完成本節。

但在您離開之前,請考慮以下使用案例是否適合您的應用程式

瞭解 Session 管理的元件

Session 管理支援由幾個協同工作的元件組成,以提供功能。這些元件是:SecurityContextHolderFilterSecurityContextPersistenceFilterSessionManagementFilter

在 Spring Security 6 中,預設不會設定 SecurityContextPersistenceFilterSessionManagementFilter。此外,任何應用程式都應該只設定 SecurityContextHolderFilterSecurityContextPersistenceFilter 其中之一,絕不能兩者都設定。

SessionManagementFilter

SessionManagementFilter 檢查 SecurityContextRepository 的內容與 SecurityContextHolder 的目前內容,以判斷使用者是否在目前的請求期間通過身份驗證,通常是通過非互動式身份驗證機制,例如預先驗證或「記住我」[1]。如果儲存庫包含安全上下文,則篩選器不執行任何操作。如果沒有,且執行緒本機 SecurityContext 包含(非匿名)Authentication 物件,則篩選器會假設它們已由堆疊中先前的篩選器進行身份驗證。然後,它將調用已組態的 SessionAuthenticationStrategy

如果目前未驗證使用者身份,則篩選器將檢查是否已請求無效的 Session ID(例如,由於逾時),並將調用已組態的 InvalidSessionStrategy(如果已設定)。最常見的行為是僅重新導向到固定的 URL,這封裝在標準實作 SimpleRedirectInvalidSessionStrategy 中。當通過命名空間組態無效 Session URL 時,也會使用後者,如先前所述

移轉離開 SessionManagementFilter

在 Spring Security 5 中,預設組態依賴 SessionManagementFilter 來偵測使用者是否剛通過身份驗證並調用 SessionAuthenticationStrategy。這樣做的問題在於,這表示在典型的設定中,必須為每個請求讀取 HttpSession

在 Spring Security 6 中,預設情況是身份驗證機制本身必須調用 SessionAuthenticationStrategy。這表示不需要偵測何時完成 Authentication,因此不需要為每個請求讀取 HttpSession

移轉離開 SessionManagementFilter 時需要考量的事項

在 Spring Security 6 中,預設不使用 SessionManagementFilter,因此,來自 sessionManagement DSL 的某些方法將不會有任何作用。

方法 替代方案

sessionAuthenticationErrorUrl

在您的身份驗證機制中組態 AuthenticationFailureHandler

sessionAuthenticationFailureHandler

在您的身份驗證機制中組態 AuthenticationFailureHandler

sessionAuthenticationStrategy

在您的身份驗證機制中組態 SessionAuthenticationStrategy,如以上討論

如果您嘗試使用任何這些方法,將會拋出例外。

自訂儲存身份驗證的位置

預設情況下,Spring Security 會為您將安全上下文儲存在 HTTP Session 中。但是,您可能想要自訂它的原因如下:

  • 您可能想要在 HttpSessionSecurityContextRepository 實例上調用個別的 setter

  • 您可能想要將安全上下文儲存在快取或資料庫中,以啟用水平擴展

首先,您需要建立 SecurityContextRepository 的實作,或使用現有的實作,例如 HttpSessionSecurityContextRepository,然後您可以在 HttpSecurity 中設定它。

自訂 SecurityContextRepository
  • Java

  • Kotlin

  • XML

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
    SecurityContextRepository repo = new MyCustomSecurityContextRepository();
    http
        // ...
        .securityContext((context) -> context
            .securityContextRepository(repo)
        );
    return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
    val repo = MyCustomSecurityContextRepository()
    http {
        // ...
        securityContext {
            securityContextRepository = repo
        }
    }
    return http.build()
}
<http security-context-repository-ref="repo">
    <!-- ... -->
</http>
<bean name="repo" class="com.example.MyCustomSecurityContextRepository" />

上述組態在 SecurityContextHolderFilter參與身份驗證篩選器(例如 UsernamePasswordAuthenticationFilter)上設定 SecurityContextRepository。若也要在無狀態篩選器中設定它,請參閱 如何自訂無狀態身份驗證的 SecurityContextRepository

如果您正在使用自訂身份驗證機制,您可能想要自行儲存 Authentication

手動儲存 Authentication

在某些情況下,例如,您可能會手動驗證使用者身份,而不是依賴 Spring Security 篩選器。您可以使用自訂篩選器或 Spring MVC 控制器 端點來執行此操作。如果您想要在請求之間儲存身份驗證(例如,在 HttpSession 中),則必須執行以下操作:

  • Java

private SecurityContextRepository securityContextRepository =
        new HttpSessionSecurityContextRepository(); (1)

@PostMapping("/login")
public void login(@RequestBody LoginRequest loginRequest, HttpServletRequest request, HttpServletResponse response) { (2)
    UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.unauthenticated(
        loginRequest.getUsername(), loginRequest.getPassword()); (3)
    Authentication authentication = authenticationManager.authenticate(token); (4)
    SecurityContext context = securityContextHolderStrategy.createEmptyContext();
    context.setAuthentication(authentication); (5)
    securityContextHolderStrategy.setContext(context);
    securityContextRepository.saveContext(context, request, response); (6)
}

class LoginRequest {

    private String username;
    private String password;

    // getters and setters
}
1 SecurityContextRepository 新增至控制器
2 注入 HttpServletRequestHttpServletResponse 以便能夠儲存 SecurityContext
3 使用提供的憑證建立未驗證的 UsernamePasswordAuthenticationToken
4 調用 AuthenticationManager#authenticate 以驗證使用者身份
5 建立 SecurityContext 並在其中設定 Authentication
6 SecurityContext 儲存在 SecurityContextRepository

就是這樣。如果您不確定上述範例中的 securityContextHolderStrategy 是什麼,您可以閱讀更多關於 使用 SecurityContextStrategy 區段的資訊。

正確清除身份驗證

如果您正在使用 Spring Security 的 登出支援,那麼它會為您處理許多事情,包括清除和儲存上下文。但是,假設您需要手動將使用者登出您的應用程式。在這種情況下,您需要確保您正確地清除和儲存上下文

為無狀態身份驗證組態持久性

有時不需要建立和維護 HttpSession,例如,跨請求持久化身份驗證。某些身份驗證機制(如 HTTP Basic)是無狀態的,因此,會在每個請求上重新驗證使用者身份。

如果您不希望建立 Session,可以使用 SessionCreationPolicy.STATELESS,如下所示:

  • Java

  • Kotlin

  • XML

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
    http
        // ...
        .sessionManagement((session) -> session
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        );
    return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
    http {
        // ...
        sessionManagement {
            sessionCreationPolicy = SessionCreationPolicy.STATELESS
        }
    }
    return http.build()
}
<http create-session="stateless">
    <!-- ... -->
</http>

上述組態正在組態 SecurityContextRepository 以使用 NullSecurityContextRepository,並且也防止請求被儲存在 Session 中

如果您正在使用 SessionCreationPolicy.NEVER,您可能會注意到應用程式仍在建立 HttpSession。在大多數情況下,發生這種情況是因為請求被儲存在 Session 中,以便在身份驗證成功後,已驗證的資源可以重新請求。為避免這種情況,請參閱如何防止請求被儲存區段。

將無狀態身份驗證儲存在 Session 中

如果由於某種原因,您正在使用無狀態身份驗證機制,但您仍然想要將身份驗證儲存在 Session 中,則可以使用 HttpSessionSecurityContextRepository 而不是 NullSecurityContextRepository

對於 HTTP Basic,您可以新增 ObjectPostProcessor,以變更 BasicAuthenticationFilter 使用的 SecurityContextRepository

將 HTTP Basic 身份驗證儲存在 HttpSession
  • Java

@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
    http
        // ...
        .httpBasic((basic) -> basic
            .addObjectPostProcessor(new ObjectPostProcessor<BasicAuthenticationFilter>() {
                @Override
                public <O extends BasicAuthenticationFilter> O postProcess(O filter) {
                    filter.setSecurityContextRepository(new HttpSessionSecurityContextRepository());
                    return filter;
                }
            })
        );

    return http.build();
}

以上內容也適用於其他身份驗證機制,例如 Bearer Token 身份驗證

瞭解需要明確儲存

在 Spring Security 5 中,預設行為是 SecurityContext 使用 SecurityContextPersistenceFilter 自動儲存到 SecurityContextRepository。儲存必須在 HttpServletResponse 提交之前且在 SecurityContextPersistenceFilter 之前完成。不幸的是,當在請求完成之前(即在提交 HttpServletResponse 之前)完成時,SecurityContext 的自動持久化可能會讓使用者感到驚訝。也很難追蹤狀態以判斷是否需要儲存,這有時會導致不必要的寫入 SecurityContextRepository(即 HttpSession)。

基於這些原因,SecurityContextPersistenceFilter 已被棄用,並由 SecurityContextHolderFilter 取代。在 Spring Security 6 中,預設行為是 SecurityContextHolderFilter 將僅從 SecurityContextRepository 讀取 SecurityContext,並將其填入 SecurityContextHolder 中。現在,如果使用者希望 SecurityContext 在請求之間持續存在,則必須使用 SecurityContextRepository 明確儲存 SecurityContext。這消除了歧義,並透過僅在必要時才需要寫入 SecurityContextRepository(即 HttpSession)來提高效能。

運作方式

總之,當 requireExplicitSavetrue 時,Spring Security 會設定 SecurityContextHolderFilter 而不是 SecurityContextPersistenceFilter

組態並行 Session 控制

如果您希望限制單一使用者登入應用程式的能力,Spring Security 透過以下簡單的新增功能提供開箱即用的支援。首先,您需要將以下監聽器新增至您的組態,以讓 Spring Security 保持更新 Session 生命周期事件的資訊:

  • Java

  • Kotlin

  • web.xml

@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
    return new HttpSessionEventPublisher();
}
@Bean
open fun httpSessionEventPublisher(): HttpSessionEventPublisher {
    return HttpSessionEventPublisher()
}
<listener>
<listener-class>
    org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>

然後將以下幾行新增至您的安全性組態:

  • Java

  • Kotlin

  • XML

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
    http
        .sessionManagement(session -> session
            .maximumSessions(1)
        );
    return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
    http {
        sessionManagement {
            sessionConcurrency {
                maximumSessions = 1
            }
        }
    }
    return http.build()
}
<http>
...
<session-management>
    <concurrency-control max-sessions="1" />
</session-management>
</http>

這將防止使用者多次登入 - 第二次登入將導致第一次登入失效。

使用 Spring Boot,您可以通過以下方式測試上述組態情境:

  • Java

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class MaximumSessionsTests {

    @Autowired
    private MockMvc mvc;

    @Test
    void loginOnSecondLoginThenFirstSessionTerminated() throws Exception {
        MvcResult mvcResult = this.mvc.perform(formLogin())
                .andExpect(authenticated())
                .andReturn();

        MockHttpSession firstLoginSession = (MockHttpSession) mvcResult.getRequest().getSession();

        this.mvc.perform(get("/").session(firstLoginSession))
                .andExpect(authenticated());

        this.mvc.perform(formLogin()).andExpect(authenticated());

        // first session is terminated by second login
        this.mvc.perform(get("/").session(firstLoginSession))
                .andExpect(unauthenticated());
    }

}

您可以使用 Maximum Sessions 範例 來嘗試。

常見的情況是您希望阻止第二次登入,在這種情況下,您可以使用:

  • Java

  • Kotlin

  • XML

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
    http
        .sessionManagement(session -> session
            .maximumSessions(1)
            .maxSessionsPreventsLogin(true)
        );
    return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
    http {
        sessionManagement {
            sessionConcurrency {
                maximumSessions = 1
                maxSessionsPreventsLogin = true
            }
        }
    }
    return http.build()
}
<http>
<session-management>
    <concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>
</http>

然後,第二次登入將被拒絕。所謂「拒絕」,是指如果使用基於表單的登入,則使用者將被傳送到 authentication-failure-url。如果第二次身份驗證通過另一個非互動式機制(例如「記住我」)進行,則會將「未經授權」(401)錯誤傳送到用戶端。如果您希望改用錯誤頁面,則可以在 session-management 元素中新增屬性 session-authentication-error-url

使用 Spring Boot,您可以通過以下方式測試上述組態情境:

  • Java

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class MaximumSessionsPreventLoginTests {

    @Autowired
    private MockMvc mvc;

    @Test
    void loginOnSecondLoginThenPreventLogin() throws Exception {
        MvcResult mvcResult = this.mvc.perform(formLogin())
                .andExpect(authenticated())
                .andReturn();

        MockHttpSession firstLoginSession = (MockHttpSession) mvcResult.getRequest().getSession();

        this.mvc.perform(get("/").session(firstLoginSession))
                .andExpect(authenticated());

        // second login is prevented
        this.mvc.perform(formLogin()).andExpect(unauthenticated());

        // first session is still valid
        this.mvc.perform(get("/").session(firstLoginSession))
                .andExpect(authenticated());
    }

}

如果您正在為基於表單的登入使用自訂身份驗證篩選器,則必須明確組態並行 Session 控制支援。您可以使用 Maximum Sessions Prevent Login 範例 來嘗試。

偵測逾時

Session 會自行過期,無需執行任何操作來確保安全上下文被移除。也就是說,Spring Security 可以偵測到 Session 何時過期,並採取您指示的特定操作。例如,當使用者使用已過期的 Session 發出請求時,您可能想要重新導向到特定的端點。這是通過 HttpSecurity 中的 invalidSessionUrl 實現的:

  • Java

  • Kotlin

  • XML

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
    http
        .sessionManagement(session -> session
            .invalidSessionUrl("/invalidSession")
        );
    return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
    http {
        sessionManagement {
            invalidSessionUrl = "/invalidSession"
        }
    }
    return http.build()
}
<http>
...
<session-management invalid-session-url="/invalidSession" />
</http>

請注意,如果您使用此機制來偵測 Session 逾時,如果使用者登出然後在不關閉瀏覽器的情況下重新登入,則可能會錯誤地報告錯誤。這是因為當您使 Session 失效時,Session Cookie 不會被清除,即使使用者已登出,也會重新提交。如果是這種情況,您可能想要組態登出以清除 Session Cookie

自訂無效 Session 策略

invalidSessionUrl 是使用 SimpleRedirectInvalidSessionStrategy 實作設定 InvalidSessionStrategy 的便利方法。如果您想要自訂行為,您可以實作 InvalidSessionStrategy 介面,並使用 invalidSessionStrategy 方法組態它:

  • Java

  • Kotlin

  • XML

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
    http
        .sessionManagement(session -> session
            .invalidSessionStrategy(new MyCustomInvalidSessionStrategy())
        );
    return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
    http {
        sessionManagement {
            invalidSessionStrategy = MyCustomInvalidSessionStrategy()
        }
    }
    return http.build()
}
<http>
...
<session-management invalid-session-strategy-ref="myCustomInvalidSessionStrategy" />
<bean name="myCustomInvalidSessionStrategy" class="com.example.MyCustomInvalidSessionStrategy" />
</http>

您可以明確刪除登出時的 JSESSIONID Cookie,例如使用登出處理程式中的 Clear-Site-Data 標頭

  • Java

  • Kotlin

  • XML

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
    http
        .logout((logout) -> logout
            .addLogoutHandler(new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(COOKIES)))
        );
    return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
    http {
        logout {
            addLogoutHandler(HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter(COOKIES)))
        }
    }
    return http.build()
}
<http>
<logout success-handler-ref="clearSiteDataHandler" />
<b:bean id="clearSiteDataHandler" class="org.springframework.security.web.authentication.logout.HeaderWriterLogoutHandler">
    <b:constructor-arg>
        <b:bean class="org.springframework.security.web.header.writers.ClearSiteDataHeaderWriter">
            <b:constructor-arg>
                <b:list>
                    <b:value>COOKIES</b:value>
                </b:list>
            </b:constructor-arg>
        </b:bean>
    </b:constructor-arg>
</b:bean>
</http>

這樣做的好處是不受容器限制,並且適用於任何支援 Clear-Site-Data 標頭的容器。

或者,您也可以在登出處理程式中使用以下語法:

  • Java

  • Kotlin

  • XML

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
    http
        .logout(logout -> logout
            .deleteCookies("JSESSIONID")
        );
    return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
    http {
        logout {
            deleteCookies("JSESSIONID")
        }
    }
    return http.build()
}
<http>
  <logout delete-cookies="JSESSIONID" />
</http>

不幸的是,不能保證這在每個 Servlet 容器中都能正常運作,因此您需要在您的環境中進行測試。

如果您在 Proxy 後面執行應用程式,您也可以通過組態 Proxy 伺服器來移除 Session Cookie。例如,通過使用 Apache HTTPD 的 mod_headers,以下指令通過使 JSESSIONID Cookie 在對登出請求的回應中過期來刪除它(假設應用程式部署在 /tutorial 路徑下):

<LocationMatch "/tutorial/logout">
Header always set Set-Cookie "JSESSIONID=;Path=/tutorial;Expires=Thu, 01 Jan 1970 00:00:00 GMT"
</LocationMatch>

更多關於 Clear Site Data登出區段 的詳細資訊。

瞭解 Session Fixation 攻擊防護

Session fixation 攻擊是一種潛在風險,惡意攻擊者有可能通過存取網站來建立 Session,然後說服另一個使用者使用相同的 Session 登入(例如,通過向他們傳送包含 Session 識別碼作為參數的連結)。Spring Security 通過在使用者登入時建立新的 Session 或以其他方式變更 Session ID 來自動防護此類攻擊。

組態 Session Fixation 防護

您可以通過在三個建議的選項之間進行選擇來控制 Session Fixation 防護的策略:

  • changeSessionId - 不建立新的 Session。而是使用 Servlet 容器提供的 Session fixation 防護 (HttpServletRequest#changeSessionId())。此選項僅在 Servlet 3.1 (Java EE 7) 和更新的容器中可用。在較舊的容器中指定它將導致例外。這是 Servlet 3.1 和更新容器中的預設值。

  • newSession - 建立新的「乾淨」Session,而不複製現有的 Session 資料(與 Spring Security 相關的屬性仍將被複製)。

  • migrateSession - 建立新的 Session,並將所有現有的 Session 屬性複製到新的 Session。這是 Servlet 3.0 或更舊容器中的預設值。

您可以通過執行以下操作來組態 Session fixation 防護:

  • Java

  • Kotlin

  • XML

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
    http
        .sessionManagement((session) -> session
            .sessionFixation((sessionFixation) -> sessionFixation
                .newSession()
            )
        );
    return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
    http {
        sessionManagement {
            sessionFixation {
                newSession()
            }
        }
    }
    return http.build()
}
<http>
  <session-management session-fixation-protection="newSession" />
</http>

當發生 Session fixation 防護時,會在應用程式上下文中發布 SessionFixationProtectionEvent。如果您使用 changeSessionId,則此防護也將導致任何 jakarta.servlet.http.HttpSessionIdListeners 收到通知,因此如果您的程式碼同時監聽這兩種事件,請謹慎使用。

您也可以將 Session fixation 防護設定為 none 以停用它,但不建議這樣做,因為這會讓您的應用程式容易受到攻擊。

使用 SecurityContextHolderStrategy

請考慮以下程式碼區塊:

  • Java

UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
        loginRequest.getUsername(), loginRequest.getPassword());
Authentication authentication = this.authenticationManager.authenticate(token);
// ...
SecurityContext context = SecurityContextHolder.createEmptyContext(); (1)
context.setAuthentication(authentication); (2)
SecurityContextHolder.setContext(context); (3)
  1. 通過靜態存取 SecurityContextHolder 來建立空的 SecurityContext 實例。

  2. SecurityContext 實例中設定 Authentication 物件。

  3. SecurityContextHolder 中靜態設定 SecurityContext 實例。

雖然上述程式碼運作良好,但可能會產生一些不良影響:當元件通過 SecurityContextHolder 靜態存取 SecurityContext 時,當有多個想要指定 SecurityContextHolderStrategy 的應用程式上下文時,可能會產生競爭條件。這是因為在 SecurityContextHolder 中,每個類別載入器有一個策略,而不是每個應用程式上下文一個策略。

為了解決這個問題,元件可以從應用程式上下文中連接 SecurityContextHolderStrategy。預設情況下,它們仍然會從 SecurityContextHolder 查找策略。

這些變更在很大程度上是內部的,但它們為應用程式提供了自動連線 SecurityContextHolderStrategy 的機會,而不是靜態存取 SecurityContext。若要這樣做,您應該將程式碼變更為以下內容:

  • Java

public class SomeClass {

    private final SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();

    public void someMethod() {
        UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.unauthenticated(
                loginRequest.getUsername(), loginRequest.getPassword());
        Authentication authentication = this.authenticationManager.authenticate(token);
        // ...
        SecurityContext context = this.securityContextHolderStrategy.createEmptyContext(); (1)
        context.setAuthentication(authentication); (2)
        this.securityContextHolderStrategy.setContext(context); (3)
    }

}
  1. 使用已組態的 SecurityContextHolderStrategy 建立空的 SecurityContext 實例。

  2. SecurityContext 實例中設定 Authentication 物件。

  3. SecurityContextHolderStrategy 中設定 SecurityContext 實例。

強制積極 Session 建立

有時,積極建立 Session 可能很有價值。這可以使用 ForceEagerSessionCreationFilter 來完成,可以使用以下方式組態:

  • Java

  • Kotlin

  • XML

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
    http
        .sessionManagement(session -> session
            .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
        );
    return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
    http {
        sessionManagement {
            sessionCreationPolicy = SessionCreationPolicy.ALWAYS
        }
    }
    return http.build()
}
<http create-session="ALWAYS">

</http>

接下來要閱讀的內容


1. 通過在身份驗證後執行重新導向的機制(例如表單登入)進行的身份驗證將不會被 SessionManagementFilter 偵測到,因為在身份驗證請求期間不會調用篩選器。Session 管理功能必須在這些情況下單獨處理。