持久化认证
GET / HTTP/1.1
Host: example.com
Cookie: SESSION=91470ce0-3f3c-455b-b7ad-079b02290f7bHTTP/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-cc1d8cb1d1c8SecurityContextRepository
在 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。
 
 在运行应用程序的其余部分之前,
 在运行应用程序的其余部分之前,SecurityContextPersistenceFilter 从 SecurityContextRepository 加载 SecurityContext 并将其设置到 SecurityContextHolder 上。
 接下来,运行应用程序。
 接下来,运行应用程序。
 最后,如果
 最后,如果 SecurityContext 发生了变化,我们会使用 SecurityContextRepository 保存 SecurityContext。这意味着在使用 SecurityContextPersistenceFilter 时,只需设置 SecurityContextHolder 即可确保 SecurityContext 使用 SecurityContextRepository 进行持久化。
在某些情况下,响应在 SecurityContextPersistenceFilter 方法完成之前就已经提交并写入客户端。例如,如果发送重定向到客户端,响应会立即写回客户端。这意味着在第 3 步中无法建立 HttpSession,因为会话 ID 无法包含在已写入的响应中。另一种可能发生的情况是,如果客户端成功认证,响应在 SecurityContextPersistenceFilter 完成之前提交,并且客户端在 SecurityContextPersistenceFilter 完成之前发出第二个请求。第二个请求中可能存在错误的认证信息。
为了避免这些问题,SecurityContextPersistenceFilter 会包装 HttpServletRequest 和 HttpServletResponse,以检测 SecurityContext 是否已更改,如果更改了,则在响应提交之前保存 SecurityContext。
SecurityContextHolderFilter
SecurityContextHolderFilter负责使用SecurityContextRepository在请求之间加载 SecurityContext。
 
 在运行应用程序的其余部分之前,
 在运行应用程序的其余部分之前,SecurityContextHolderFilter 从 SecurityContextRepository 加载 SecurityContext 并将其设置到 SecurityContextHolder 上。
 接下来,运行应用程序。
 接下来,运行应用程序。
与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)