身份验证持久性和会话管理

一旦你有一个认证请求的应用程序,考虑如何持久化并恢复该认证以用于未来的请求是很重要的。

默认情况下,这是自动完成的,因此不需要额外的代码,尽管了解HttpSecurityrequireExplicitSave的含义很重要。

如果你愿意,你可以阅读更多关于requireExplicitSave的作用它为何重要。否则,在大多数情况下,你已经完成了本节内容。

但在离开之前,请考虑以下任何用例是否适合你的应用程序

了解会话管理的组件

会话管理支持由一些协同工作的组件组成,以提供功能。这些组件是SecurityContextHolderFilterSecurityContextPersistenceFilterSessionManagementFilter

在Spring Security 6中,SecurityContextPersistenceFilterSessionManagementFilter默认未设置。此外,任何应用程序都应该只设置SecurityContextHolderFilterSecurityContextPersistenceFilter,两者不能同时设置。

SessionManagementFilter

SessionManagementFilter根据SecurityContextHolder的当前内容检查SecurityContextRepository的内容,以确定用户是否在当前请求期间进行了身份验证,通常是通过非交互式身份验证机制,例如预身份验证或记住我[1]。如果仓库包含安全上下文,过滤器不执行任何操作。如果它不包含,并且线程本地SecurityContext包含一个(非匿名)Authentication对象,过滤器假定它们已被堆栈中的前一个过滤器进行身份验证。然后它将调用配置的SessionAuthenticationStrategy

如果用户当前未进行身份验证,过滤器将检查是否请求了无效的会话ID(例如,由于超时),并且如果设置了InvalidSessionStrategy,则会调用它。最常见的行为是重定向到固定URL,这封装在标准实现SimpleRedirectInvalidSessionStrategy中。后者也用于通过命名空间配置无效会话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 会话中。但是,您可能出于以下几个原因想要自定义它

  • 您可能希望在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,是无状态的,因此在每个请求上都会重新验证用户。

如果您不希望创建会话,可以使用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,并且还阻止了请求被保存到会话中

如果您正在使用SessionCreationPolicy.NEVER,您可能会注意到应用程序仍在创建HttpSession。在大多数情况下,这是因为请求被保存到会话中,以便在身份验证成功后重新请求已认证的资源。为了避免这种情况,请参阅如何阻止请求被保存一节。

将会话中的无状态身份验证存储

如果由于某种原因,您正在使用无状态身份验证机制,但仍然希望将会话中的身份验证存储起来,则可以使用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 Authentication

理解显式保存要求

在 Spring Security 5 中,默认行为是SecurityContext自动保存到SecurityContextRepository中,通过SecurityContextPersistenceFilter完成。保存必须在HttpServletResponse提交之前和SecurityContextPersistenceFilter之前完成。不幸的是,SecurityContext的自动持久化可能会让用户感到意外,因为它在请求完成之前(即在提交HttpServletResponse之前)完成。跟踪状态以确定是否需要保存也很复杂,有时会导致不必要的写入SecurityContextRepository(即HttpSession)。

由于这些原因,SecurityContextPersistenceFilter已被弃用,取而代之的是SecurityContextHolderFilter。在Spring Security 6中,默认行为是SecurityContextHolderFilter只会从SecurityContextRepository中读取SecurityContext并将其填充到SecurityContextHolder中。现在,如果用户希望SecurityContext在请求之间持久化,则必须使用SecurityContextRepository显式保存SecurityContext。这消除了歧义,并通过仅在必要时才写入SecurityContextRepository(即HttpSession)来提高性能。

工作原理

总而言之,当requireExplicitSavetrue时,Spring Security 会设置SecurityContextHolderFilter,而不是SecurityContextPersistenceFilter

配置并发会话控制

如果您希望对单个用户登录应用程序的能力施加限制,Spring Security 通过以下简单的添加即可提供开箱即用的支持。首先,您需要在配置中添加以下监听器,以使 Spring Security 了解会话生命周期事件

  • 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>

这将阻止用户多次登录 - 第二次登录将导致第一次登录失效。

您还可以根据用户的身份进行调整。例如,管理员可能能够拥有多个会话

  • Java

  • Kotlin

  • XML

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
	AuthorizationManager<?> isAdmin = AuthorityAuthorizationManager.hasRole("ADMIN");
    http
        .sessionManagement((session) -> session
            .maximumSessions((authentication) -> isAdmin.authorize(() -> authentication, null).isGranted() ? -1 : 1)
        );
    return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
    val isAdmin: AuthorizationManager<*> = AuthorityAuthorizationManager.hasRole("ADMIN")
    http {
        sessionManagement {
            sessionConcurrency {
                maximumSessions {
                    authentication -> if (isAdmin.authorize({ authentication }, null)!!.isGranted) -1 else 1
                }
            }
        }
    }
    return http.build()
}
<http>
...
<session-management>
    <concurrency-control max-sessions-ref="sessionLimit" />
</session-management>
</http>

<b:bean id="sessionLimit" class="my.SessionLimitImplementation"/>

使用 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());
    }

}

您可以使用最大会话示例进行尝试。

通常,您更希望阻止第二次登录,在这种情况下,您可以使用

  • 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-authentication-error-url添加到session-management元素中。

使用 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());
    }

}

如果您正在使用基于表单登录的自定义身份验证过滤器,则必须显式配置并发会话控制支持。您可以使用最大会话阻止登录示例进行尝试。

如果您正在使用UserDetails的自定义实现,请确保您重写了equals()hashCode()方法。Spring Security 中的默认SessionRegistry实现依赖于使用这些方法来正确识别和管理用户会话的内存中 Map。未能重写它们可能会导致会话跟踪和用户比较行为异常的问题。

检测超时

会话会自动过期,无需采取任何措施来确保安全上下文被移除。话虽如此,Spring Security 可以检测会话何时过期并采取您指定的特定操作。例如,当用户使用已过期的会话发出请求时,您可能希望重定向到特定的端点。这可以通过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>

请注意,如果您使用此机制来检测会话超时,如果用户注销后又在不关闭浏览器的情况下重新登录,它可能会错误地报告错误。这是因为会话无效时不会清除会话 cookie,即使用户已注销,该 cookie 仍会重新提交。如果出现这种情况,您可能需要配置注销以清除会话 cookie

自定义无效会话策略

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 header

  • 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 容器,因此您需要在自己的环境中进行测试。

如果您的应用程序在代理服务器后面运行,您还可以通过配置代理服务器来删除会话 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>

有关清除站点数据注销部分的更多详细信息。

理解会话固定攻击防护

会话固定攻击是一种潜在的风险,恶意攻击者可以通过访问网站创建会话,然后说服另一个用户使用相同的会话登录(例如,通过向他们发送包含会话标识符作为参数的链接)。Spring Security 通过在用户登录时创建新会话或更改会话 ID 来自动防范此问题。

配置会话固定保护

您可以通过选择以下三种推荐选项来控制会话固定保护策略

  • changeSessionId - 不创建新会话。相反,使用 Servlet 容器提供的会话固定保护(HttpServletRequest#changeSessionId())。此选项仅在 Servlet 3.1(Java EE 7)及更高版本的容器中可用。在旧版本容器中指定它将导致异常。这是 Servlet 3.1 及更高版本容器中的默认设置。

  • newSession - 创建一个“干净”的新会话,不复制现有会话数据(Spring Security 相关属性仍将被复制)。

  • migrateSession - 创建一个新会话并将所有现有会话属性复制到新会话。这是 Servlet 3.0 或更早版本容器中的默认设置。

您可以通过以下方式配置会话固定保护

  • 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>

当会话固定保护发生时,它会导致在应用程序上下文中发布SessionFixationProtectionEvent。如果您使用changeSessionId,此保护**还会**通知任何jakarta.servlet.http.HttpSessionIdListener,因此如果您的代码同时监听这两个事件,请谨慎使用。

您还可以将会话固定保护设置为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实例。

强制提前创建会话

有时,提前创建会话可能很有价值。这可以通过使用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检测到,因为在认证请求期间不会调用该过滤器。在这些情况下,会话管理功能必须单独处理。
© . This site is unofficial and not affiliated with VMware.