此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Security 6.4.1spring-doc.cn

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

一旦您有一个正在对请求进行身份验证的应用程序,重要的是要考虑如何在将来的请求中保留和恢复生成的身份验证。spring-doc.cn

默认情况下,这是自动完成的,因此不需要额外的代码,但您应该考虑一些步骤。第一种是在 中设置属性。 您可以这样做:requireExplicitSaveHttpSecurityspring-doc.cn

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
    http
        // ...
        .securityContext((context) -> context
            .requireExplicitSave(true)
        );
    return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
    http {
        // ...
        securityContext {
            requireExplicitSave = true
        }
    }
    return http.build()
}
<http security-context-explicit-save="true">
    <!-- ... -->
</http>

最直接的原因是它正在成为 6.0 中的默认值,因此这将确保您为此做好准备。spring-doc.cn

如果你愿意,你可以阅读更多关于 requireExplicitSave 的作用它为什么重要。否则,在大多数情况下,您已完成此部分。spring-doc.cn

但在您离开之前,请考虑以下用例是否适合您的应用程序:spring-doc.cn

了解会话管理的组件

Session Management 支持由几个组件组成,这些组件协同工作以提供功能。 这些组件是SecurityContextHolderFilterSecurityContextPersistenceFilterSessionManagementFilterspring-doc.cn

在 Spring Security 6 中,默认情况下不设置 and。 除此之外,任何应用程序都应该只具有 one 或 set,而不能同时具有两者。SecurityContextPersistenceFilterSessionManagementFilterSecurityContextHolderFilterSecurityContextPersistenceFilterspring-doc.cn

SessionManagementFilter

该 根据 的当前内容检查 的内容,以确定用户是否在当前请求期间已经过身份验证,通常是通过非交互式身份验证机制,例如 pre-authentication 或 remember-meSessionManagementFilterSecurityContextRepositorySecurityContextHolder[1]. 如果存储库包含安全上下文,则筛选器不执行任何操作。 如果不是,并且 thread-local 包含一个 (非匿名) 对象,则 filter 会假定它们已经通过堆栈中前一个 filter 的身份验证。 然后,它将调用配置的 .SecurityContextAuthenticationSessionAuthenticationStrategyspring-doc.cn

如果用户当前未进行身份验证,则过滤器将检查是否请求了无效的会话 ID(例如,由于超时),并将调用配置的 ,如果已设置。 最常见的行为只是重定向到固定的 URL,这封装在 标准实施 中。 如前所述,当通过命名空间配置无效的会话 URL 时,也会使用后者。InvalidSessionStrategySimpleRedirectInvalidSessionStrategyspring-doc.cn

远离SessionManagementFilter

在 Spring Security 5 中,默认配置依赖于检测用户是否刚刚进行身份验证并调用SessionAuthenticationStrategy。 这样做的问题在于,这意味着在典型设置中,必须为每个请求读取 the。SessionManagementFilterHttpSessionspring-doc.cn

在 Spring Security 6 中,默认情况下,身份验证机制本身必须调用 . 这意味着无需检测何时完成,因此不需要为每个请求读取 。SessionAuthenticationStrategyAuthenticationHttpSessionspring-doc.cn

要选择新的 Spring Security 6 默认值,应使用以下配置。spring-doc.cn

需要显式调用SessionAuthenticationStrategy
@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
    http
        // ...
        .sessionManagement((sessions) -> sessions
            .requireExplicitAuthenticationStrategy(true)
        );
    return http.build();
}
@Bean
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
    http {
        sessionManagement {
            requireExplicitAuthenticationStrategy = true
        }
    }
    return http.build()
}
<http>
    <!-- ... -->
    <session-management authentication-strategy-explicit-invocation="true"/>
</http>

搬离时要考虑的事项SessionManagementFilter

When 时,表示不会使用 ,因此,DSL 中的某些方法不会产生任何效果。requireExplicitAuthenticationStrategy = trueSessionManagementFiltersessionManagementspring-doc.cn

方法 更换

sessionAuthenticationErrorUrlspring-doc.cn

在身份验证机制中配置 AuthenticationFailureHandlerspring-doc.cn

sessionAuthenticationFailureHandlerspring-doc.cn

在身份验证机制中配置 AuthenticationFailureHandlerspring-doc.cn

sessionAuthenticationStrategyspring-doc.cn

如上所述,在身份验证机制中配置 anSessionAuthenticationStrategyspring-doc.cn

在 Spring Security 6 中,如果你尝试在(默认)时使用这些方法中的任何一个,将引发异常。requireExplicitAuthenticationStrategy = truespring-doc.cn

自定义身份验证的存储位置

默认情况下,Spring Security 将安全上下文存储在 HTTP 会话中。但是,以下是您可能希望自定义该自定义的几个原因:spring-doc.cn

  • 您可能希望在实例上调用单个 setterHttpSessionSecurityContextRepositoryspring-doc.cn

  • 您可能希望将安全上下文存储在缓存或数据库中,以启用水平扩展spring-doc.cn

首先,您需要创建 的实现或使用现有实现(如 ),然后您可以在 中设置它。SecurityContextRepositoryHttpSessionSecurityContextRepositoryHttpSecurityspring-doc.cn

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

上述配置设置 on 和 participating 身份验证过滤器,如 . 要在无状态过滤器中也设置它,请参阅如何自定义 SecurityContextRepository 以进行无状态身份验证SecurityContextRepositorySecurityContextHolderFilterUsernamePasswordAuthenticationFilterspring-doc.cn

如果您使用的是自定义身份验证机制,则可能需要自己存储 Authenticationspring-doc.cn

手动存储Authentication

例如,在某些情况下,您可能正在手动对用户进行身份验证,而不是依赖 Spring Security 过滤器。 你可以使用自定义过滤器或 Spring MVC 控制器端点来做到这一点。 如果要保存请求之间的身份验证,例如,在 中,您必须这样做:HttpSessionspring-doc.cn

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 注入 和 为了能够保存HttpServletRequestHttpServletResponseSecurityContext
3 使用提供的凭证创建未经身份验证的UsernamePasswordAuthenticationToken
4 用于对用户进行身份验证的调用AuthenticationManager#authenticate
5 创建一个并在其中设置SecurityContextAuthentication
6 将 保存在SecurityContextSecurityContextRepository

就是这样。 如果您不确定上述示例中的内容,可以在使用 SecurityContextStrategy 部分中阅读有关它的更多信息。securityContextHolderStrategyspring-doc.cn

正确清除身份验证

如果您使用的是 Spring Security 的 Logout Support,那么它会为您处理很多事情,包括清除和保存上下文。 但是,假设您需要手动将用户从您的应用程序中注销。在这种情况下,您需要确保正确清除和保存上下文。spring-doc.cn

现在,您可能已经熟悉如何通过执行 . 这很好,但是如果您的应用程序需要显式保存上下文,那么仅仅清除它是不够的。 原因是它不会将其从 中删除,这意味着 仍然可以用于下一个请求,我们绝对不希望这样。SecurityContextHolderSecurityContextHolderStrategy#clearContext()SecurityContextRepositorySecurityContextspring-doc.cn

为了确保身份验证被正确清除和保存,你可以调用 SecurityContextLogoutHandler 来为我们执行此操作,如下所示:spring-doc.cn

SecurityContextLogoutHandler handler = new SecurityContextLogoutHandler(); (1)
handler.logout(httpServletRequest, httpServletResponse, null); (2)
1 创建一个新的SecurityContextLogoutHandler
2 调用传入 和 authentication 的方法,因为此处理程序不需要它。logoutHttpServletRequestHttpServletResponsenull

重要的是要记住,清除和保存上下文只是注销过程的一部分,因此我们建议让 Spring Security 来处理它。spring-doc.cn

为无状态身份验证配置持久性

有时不需要创建和维护 例如,在请求之间持久化身份验证。 某些身份验证机制(如 HTTP Basic)是无状态的,因此,在每个请求上都会重新验证用户。HttpSessionspring-doc.cn

如果您不想创建会话,可以使用 ,如下所示:SessionCreationPolicy.STATELESSspring-doc.cn

@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配置为使用a,并且还阻止了请求被保存在会话中NullSecurityContextRepositoryspring-doc.cn

如果您使用的是 ,您可能会注意到应用程序仍在创建一个 . 在大多数情况下,发生这种情况是因为请求保存在会话中,以便经过身份验证的资源在身份验证成功后重新请求。 为避免这种情况,请参考 如何防止被保存请求 部分。SessionCreationPolicy.NEVERHttpSessionspring-doc.cn

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

如果出于某种原因,您使用的是无状态身份验证机制,但仍希望将身份验证存储在会话中,则可以使用 代替 .HttpSessionSecurityContextRepositoryNullSecurityContextRepositoryspring-doc.cn

对于 HTTP Basic,您可以添加一个 ObjectPostProcessor,该 ObjectPostProcessorSecurityContextRepositoryBasicAuthenticationFilterspring-doc.cn

将 HTTP Basic 身份验证存储在HttpSession
@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 Authenticationspring-doc.cn

了解需要显式保存

在 Spring Security 5 中,默认行为是使用SecurityContextPersistenceFilterSecurityContext自动保存到SecurityContextRepository中。 必须在提交之前和之前完成保存。 遗憾的是,如果在请求完成之前(即在提交 之前)完成 的自动持久化,则可能会让用户感到惊讶。 跟踪状态以确定是否需要保存也很复杂,有时会导致对 (即 ) 进行不必要的写入。HttpServletResponseSecurityContextPersistenceFilterSecurityContextHttpServletResponseSecurityContextRepositoryHttpSessionspring-doc.cn

由于这些原因,已弃用 ,取而代之的是 . 在 Spring Security 6 中,默认行为是 SecurityContextHolderFilter 将仅读取 from 并将其填充到 . 如果用户希望 在请求之间持久保存,现在必须显式保存 。 这消除了歧义并提高了性能,因为只需要在必要时写入 (即 )。SecurityContextPersistenceFilterSecurityContextHolderFilterSecurityContextSecurityContextRepositorySecurityContextHolderSecurityContextSecurityContextRepositorySecurityContextSecurityContextRepositoryHttpSessionspring-doc.cn

运作方式

总之,当 is 时,Spring Security 会设置 SecurityContextHolderFilter 而不是 SecurityContextPersistenceFilterrequireExplicitSavetruespring-doc.cn

配置并发会话控制

如果您希望对单个用户登录应用程序的能力施加限制,Spring Security 通过以下简单的添加来支持开箱即用。 首先,您需要将以下侦听器添加到您的配置中,以使 Spring Security 了解会话生命周期事件:spring-doc.cn

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

然后将以下行添加到您的安全配置中:spring-doc.cn

@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-doc.cn

使用 Spring Boot,您可以通过以下方式测试上述配置场景:spring-doc.cn

@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 示例进行尝试。spring-doc.cn

您通常希望阻止第二次登录,在这种情况下,您可以使用:spring-doc.cn

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

然后,第二次登录将被拒绝。 “rejected” 是指如果使用基于表单的登录,则用户将被发送到 。 如果第二次身份验证通过另一个非交互式机制进行,例如“remember-me”,则会向客户端发送“未授权”(401) 错误。 如果您想使用错误页面,则可以将属性添加到元素中。authentication-failure-urlsession-authentication-error-urlsession-managementspring-doc.cn

使用 Spring Boot,您可以通过以下方式测试上述配置:spring-doc.cn

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

}

如果您正在为基于表单的登录使用自定义身份验证过滤器,则必须显式配置并发会话控制支持。 您可以使用 Maximum Sessions Prevent Login 示例进行尝试。spring-doc.cn

检测超时

会话会自行过期,无需执行任何操作来确保删除安全上下文。 也就是说,Spring Security 可以检测会话何时过期并采取您指示的特定操作。 例如,您可能希望在用户使用已过期的会话发出请求时重定向到特定终端节点。 这是通过 in 实现的:invalidSessionUrlHttpSecurityspring-doc.cn

@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。 如果是这种情况,您可能希望配置 logout 以清除会话 Cookiespring-doc.cn

自定义无效会话策略

这是使用 SimpleRedirectInvalidSessionStrategy 实现进行设置的便捷方法。 如果要自定义行为,可以实现 InvalidSessionStrategy 接口,并使用以下方法对其进行配置:invalidSessionUrlInvalidSessionStrategyinvalidSessionStrategyspring-doc.cn

@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 标头spring-doc.cn

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

这样做的优点是与容器无关,并且适用于任何支持 header 的容器。Clear-Site-Dataspring-doc.cn

作为替代方法,您还可以在 logout 处理程序中使用以下语法:spring-doc.cn

@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 容器,因此您需要在您的环境中对其进行测试。spring-doc.cn

如果您在代理后面运行应用程序,则还可以通过配置代理服务器来删除会话 cookie。 例如,通过使用 Apache HTTPD 的 ,以下指令通过在对 logout 请求的响应中使 cookie 过期来删除 cookie(假设应用程序部署在 path下):mod_headersJSESSIONID/tutorialspring-doc.cn

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

有关清除站点数据和注销部分的更多详细信息。spring-doc.cn

了解 Session Fixation 攻击防护

会话固定攻击是一种潜在风险,恶意攻击者可能通过访问站点来创建会话,然后说服其他用户使用相同的会话登录(例如,通过向他们发送包含会话标识符作为参数的链接)。 Spring Security 通过在用户登录时创建新会话或更改会话 ID 来自动防止这种情况。spring-doc.cn

配置 Session Fixation Protection

您可以通过在三个建议选项之间进行选择来控制 Session Fixation Protection 的策略:spring-doc.cn

  • changeSessionId- 不要创建新会话。 请改用 Servlet 容器 () 提供的会话固定保护。 此选项仅在 Servlet 3.1 (Java EE 7) 和更新的容器中可用。 在较旧的容器中指定它将导致异常。 这是 Servlet 3.1 和更高版本中的默认设置。HttpServletRequest#changeSessionId()spring-doc.cn

  • newSession- 创建一个新的 “clean” session,而不复制现有的 session 数据(与 Spring Security 相关的属性仍然会被复制)。spring-doc.cn

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

您可以通过执行以下操作来配置会话固定保护:spring-doc.cn

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

当发生会话固定保护时,它会导致 在应用程序上下文中发布。 如果使用 ,则此保护会导致通知任何 s,因此,如果代码侦听这两个事件,请谨慎使用。SessionFixationProtectionEventchangeSessionIdjakarta.servlet.http.HttpSessionIdListenerspring-doc.cn

您还可以将会话固定保护设置为以禁用它,但不建议这样做,因为它会使您的应用程序容易受到攻击。nonespring-doc.cn

SecurityContextHolderStrategy

请考虑以下代码块:spring-doc.cn

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(authentication); (3)
  1. 通过静态访问 来创建空实例。SecurityContextSecurityContextHolderspring-doc.cn

  2. 设置实例中的对象。AuthenticationSecurityContextspring-doc.cn

  3. 在 static .SecurityContextSecurityContextHolderspring-doc.cn

虽然上述代码运行良好,但它可能会产生一些不希望的效果:当组件通过 静态访问 时,当有多个应用程序上下文想要指定 . 这是因为每个 classloader 都有一个策略,而不是每个应用程序上下文一个策略。SecurityContextSecurityContextHolderSecurityContextHolderStrategySecurityContextHolderspring-doc.cn

为了解决这个问题,组件可以从应用程序上下文进行连接。 默认情况下,他们仍将从 中查找策略。SecurityContextHolderStrategySecurityContextHolderspring-doc.cn

这些更改主要是内部的,但它们为应用程序提供了自动装配的机会,而不是静态访问 。 为此,您应该将代码更改为以下内容:SecurityContextHolderStrategySecurityContextspring-doc.cn

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. 使用配置的 .SecurityContextSecurityContextHolderStrategyspring-doc.cn

  2. 设置实例中的对象。AuthenticationSecurityContextspring-doc.cn

  3. 在 .SecurityContextSecurityContextHolderStrategyspring-doc.cn

强制创建 Eager Session

有时,急切地创建会话可能很有价值。 这可以通过使用 ForceEagerSessionCreationFilter 来完成,该过滤器可以使用以下方法进行配置:spring-doc.cn

@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. 验证后执行重定向的机制(如 form-login)的验证不会被检测到,因为在验证请求期间不会调用过滤器。在这些情况下,必须单独处理会话管理功能。SessionManagementFilter