持久化认证
示例 1. 未经身份验证的用户请求受保护资源
GET / HTTP/1.1
Host: example.com
Cookie: SESSION=91470ce0-3f3c-455b-b7ad-079b02290f7b
HTTP/1.1 302 Found
Location: /login
用户提交其用户名和密码。
POST /login HTTP/1.1
Host: example.com
Cookie: SESSION=91470ce0-3f3c-455b-b7ad-079b02290f7b
username=user&password=password&_csrf=35942e65-a172-4cd4-a1d4-d16a51147b3e
用户通过身份验证后,系统会为其关联一个新的会话ID,以防止会话固定攻击。
HTTP/1.1 302 Found
Location: /
Set-Cookie: SESSION=4c66e474-3f5a-43ed-8e48-cc1d8cb1d1c8; Path=/; HttpOnly; SameSite=Lax
后续请求会包含会话cookie,该cookie用于在会话剩余时间内对用户进行身份验证。
GET / HTTP/1.1
Host: example.com
Cookie: SESSION=4c66e474-3f5a-43ed-8e48-cc1d8cb1d1c8
SecurityContextRepository
在 Spring Security 中,用户与后续请求的关联是通过 SecurityContextRepository 建立的。SecurityContextRepository 的默认实现是 DelegatingSecurityContextRepository,它会委托给以下组件:
HttpSessionSecurityContextRepository
HttpSessionSecurityContextRepository 将 SecurityContext 与 HttpSession 关联起来。如果用户希望以其他方式或完全不将用户与后续请求关联,可以用 SecurityContextRepository 的另一个实现来替换 HttpSessionSecurityContextRepository。
NullSecurityContextRepository
如果不希望将 SecurityContext 与 HttpSession 关联(例如在使用 OAuth 进行身份验证时),NullSecurityContextRepository 是一个 SecurityContextRepository 的实现,它不执行任何操作。
RequestAttributeSecurityContextRepository
RequestAttributeSecurityContextRepository 将 SecurityContext 保存为请求属性,以确保 SecurityContext 在可能清除 SecurityContext 的跨调度类型的单个请求中可用。
例如,假设客户端发起请求、通过身份验证后发生错误。根据Servlet容器的实现,该错误意味着已建立的任何SecurityContext都会被清除,然后进行错误分发。当进行错误分发时,并没有建立SecurityContext。这意味着除非以某种方式持久化SecurityContext,否则错误页面无法使用SecurityContext进行授权或显示当前用户信息。
- Java
- XML
public SecurityFilterChain filterChain(HttpSecurity http) {
http
// ...
.securityContext((securityContext) -> securityContext
.securityContextRepository(new RequestAttributeSecurityContextRepository())
);
return http.build();
}
<http security-context-repository-ref="contextRepository">
<!-- ... -->
</http>
<b:bean name="contextRepository"
class="org.springframework.security.web.context.RequestAttributeSecurityContextRepository" />
DelegatingSecurityContextRepository
DelegatingSecurityContextRepository 将 SecurityContext 保存到多个 SecurityContextRepository 委托中,并允许按指定顺序从任意一个委托中检索。
最实用的配置方式如下例所示,它允许同时使用 RequestAttributeSecurityContextRepository 和 HttpSessionSecurityContextRepository。
- Java
- Kotlin
- XML
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// ...
.securityContext((securityContext) -> securityContext
.securityContextRepository(new DelegatingSecurityContextRepository(
new RequestAttributeSecurityContextRepository(),
new HttpSessionSecurityContextRepository()
))
);
return http.build();
}
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
// ...
securityContext {
securityContextRepository = DelegatingSecurityContextRepository(
RequestAttributeSecurityContextRepository(),
HttpSessionSecurityContextRepository()
)
}
}
return http.build()
}
<http security-context-repository-ref="contextRepository">
<!-- ... -->
</http>
<bean name="contextRepository"
class="org.springframework.security.web.context.DelegatingSecurityContextRepository">
<constructor-arg>
<bean class="org.springframework.security.web.context.RequestAttributeSecurityContextRepository" />
</constructor-arg>
<constructor-arg>
<bean class="org.springframework.security.web.context.HttpSessionSecurityContextRepository" />
</constructor-arg>
</bean>
在 Spring Security 6 中,上面展示的示例是默认配置。
SecurityContextPersistenceFilter
SecurityContextPersistenceFilter 负责使用 SecurityContextRepository 在请求之间持久化 SecurityContext。

1 在运行应用程序的其余部分之前,SecurityContextPersistenceFilter 会从 SecurityContextRepository 加载 SecurityContext,并将其设置在 SecurityContextHolder 上。
2 接下来,运行应用程序。
3 最后,如果 SecurityContext 已发生变更,我们会使用 SecurityContextRepository 来保存 SecurityContext。这意味着,在使用 SecurityContextPersistenceFilter 时,只需设置 SecurityContextHolder 即可确保 SecurityContext 通过 SecurityContextRepository 持久化保存。
在某些情况下,响应会在 SecurityContextPersistenceFilter 方法完成之前被提交并写入客户端。例如,如果向客户端发送了重定向,响应会立即写回客户端。这意味着在第 3 步中可能无法建立 HttpSession,因为会话 ID 无法包含在已经写入的响应中。另一种可能发生的情况是,如果客户端成功认证,响应会在 SecurityContextPersistenceFilter 完成之前被提交,并且客户端在 SecurityContextPersistenceFilter 完成之前发出了第二个请求,那么第二个请求中可能会出现错误的认证信息。
为了避免这些问题,SecurityContextPersistenceFilter 会同时包装 HttpServletRequest 和 HttpServletResponse,以检测 SecurityContext 是否发生变化,并在响应提交前保存已变更的 SecurityContext。
SecurityContextHolderFilter
SecurityContextHolderFilter 负责使用 SecurityContextRepository 在请求之间加载 SecurityContext。

1 在运行应用程序的其余部分之前,SecurityContextHolderFilter 会从 SecurityContextRepository 加载 SecurityContext,并将其设置在 SecurityContextHolder 上。
2 接下来,运行该应用程序。
与 SecurityContextPersistenceFilter 不同,SecurityContextHolderFilter 仅加载 SecurityContext,而不会保存 SecurityContext。这意味着在使用 SecurityContextHolderFilter 时,需要显式地保存 SecurityContext。
- Java
- Kotlin
- XML
public SecurityFilterChain filterChain(HttpSecurity http) {
http
// ...
.securityContext((securityContext) -> securityContext
.requireExplicitSave(true)
);
return http.build();
}
@Bean
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
http {
securityContext {
requireExplicitSave = true
}
}
return http.build()
}
<http security-context-explicit-save="true">
<!-- ... -->
</http>
在使用该配置时,必须注意:任何通过 SecurityContext 设置 SecurityContextHolder 的代码,如果需要在请求之间持久化 SecurityContext,也应将其保存到 SecurityContextRepository 中。
例如,以下代码:
- Java
- Kotlin
SecurityContextHolder.setContext(securityContext);
SecurityContextHolder.setContext(securityContext)
应替换为
- Java
- Kotlin
SecurityContextHolder.setContext(securityContext);
securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse);
SecurityContextHolder.setContext(securityContext)
securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse)