Persisting Authentication

用户第一次请求受保护资源时,就会 prompted for credentials。提示输入凭证的最常见方法之一是将用户重定向到 log in page。针对请求受保护资源的未经验证用户进行的 HTTP 交换摘要如下: .Unauthenticated User Requests Protected Resource

GET / HTTP/1.1
Host: example.com
Cookie: SESSION=91470ce0-3f3c-455b-b7ad-079b02290f7b
HTTP/1.1 302 Found
Location: /login

用户提交他们的用户名和密码。

Username and Password Submitted
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 以防止 session fixation attacks

Authenticated User is Associated to New Session
HTTP/1.1 302 Found
Location: /
Set-Cookie: SESSION=4c66e474-3f5a-43ed-8e48-cc1d8cb1d1c8; Path=/; HttpOnly; SameSite=Lax

后续请求包括用于在会话剩余时间内对用户进行身份验证的会话 Cookie。

Authenticated Session Provided as Credentials
GET / HTTP/1.1
Host: example.com
Cookie: SESSION=4c66e474-3f5a-43ed-8e48-cc1d8cb1d1c8

SecurityContextRepository

在 Spring 安全中,使用 {security-api-url}org/springframework/security/web/context/SecurityContextRepository.html[SecurityContextRepository] 将用户与未来请求关联。SecurityContextRepository`的默认实现是 {security-api-url}org/springframework/security/web/context/DelegatingSecurityContextRepository.html[`DelegatingSecurityContextRepository],它委托给以下内容:

  • <<`HttpSessionSecurityContextRepository`,httpsecuritycontextrepository>>

  • <<`RequestAttributeSecurityContextRepository`,requestattributesecuritycontextrepository>>

HttpSessionSecurityContextRepository

{security-api-url}org/springframework/security/web/context/HttpSessionSecurityContextRepository.html[HttpSessionSecurityContextRepository] 会将 SecurityContext关联到 HttpSession。如果要以其他方式或根本不将用户关联到后续请求,则用户可以用 SecurityContextRepository`的另一个实现替换 `HttpSessionSecurityContextRepository

NullSecurityContextRepository

如果不想将 SecurityContext`与 `HttpSession`关联(即使用 OAuth 进行身份验证时),则 {security-api-url}org/springframework/security/web/context/NullSecurityContextRepository.html[`NullSecurityContextRepository] 是 `SecurityContextRepository`的一个实现,不执行任何操作。

RequestAttributeSecurityContextRepository

{security-api-url}org/springframework/security/web/context/RequestAttributeSecurityContextRepository.html[RequestAttributeSecurityContextRepository] 将 SecurityContext 保存为请求属性,以确保 SecurityContext 对跨越可能清除 SecurityContext 的分派类型的单个请求可用。

例如,假设一个客户端发出请求,已过身份验证,随后出现错误。具体取决于 servlet 容器实现,该错误表示任何已建立的 SecurityContext 均已清除,然后执行错误分派。当执行错误分派时,没有建立 SecurityContext。这意味着错误页面无法使用 SecurityContext 进行授权或显示当前用户,除非 SecurityContext 以某种方式持久化。

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

{security-api-url}org/springframework/security/web/context/DelegatingSecurityContextRepository.html[DelegatingSecurityContextRepository] 将 SecurityContext 保存到多个 SecurityContextRepository 委托,并允许按指定顺序从任何委托中检索。

对此最有用的安排是使用以下示例配置的,它同时允许使用 <<`RequestAttributeSecurityContextRepository`,requestattributesecuritycontextrepository>> 和 <<`HttpSessionSecurityContextRepository`,httpsecuritycontextrepository>>。

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

{security-api-url}org/springframework/security/web/context/SecurityContextPersistenceFilter.html[SecurityContextPersistenceFilter] 负责使用 SecurityContextRepository在请求之间持久存储 SecurityContext

securitycontextpersistencefilter

number 1 在运行应用程序的其余部分之前,SecurityContextPersistenceFilterSecurityContextRepository 中加载 SecurityContext 并将其设置到 SecurityContextHolder 上。

number 2 接下来,运行应用程序。

number 3 最后,如果 SecurityContext 已更改,则使用 SecurityContextPersistenceRepository 保存 SecurityContext。这意味着在使用 SecurityContextPersistenceFilter 时,只需设置 SecurityContextHolder,即可确保使用 SecurityContextRepositorySecurityContext 进行持久化。

在某些情况下,会在 SecurityContextPersistenceFilter 方法完成之前提交响应并将其写入客户端。例如,如果向客户端发送重定向,则立即将该响应写回客户端。这意味着由于会话 ID 不能包含在已写入的响应中,因此在步骤 3 中建立 HttpSession 是不可能的。可能发生的另一情况是如果某个客户端成功认证,那么在 SecurityContextPersistenceFilter 完成之前会提交响应,并且该客户端在 SecurityContextPersistenceFilter 完成之前发起第二个请求,则错误的身份验证可能出现在第二个请求中。

为了避免这些问题,SecurityContextPersistenceFilterHttpServletRequestHttpServletResponse 都同时封装,侦测 SecurityContext 是否已更改,如果已更改,就在响应提交之前保存 SecurityContext

SecurityContextHolderFilter

{security-api-url}org/springframework/security/web/context/SecurityContextHolderFilter.html[SecurityContextHolderFilter] 负责使用 SecurityContextRepository在请求之间加载 SecurityContext

securitycontextholderfilter

在运行应用程序的其余部分之前,SecurityContextHolderFilter 会从 SecurityContextRepository 加载 SecurityContext 并将其设定在 SecurityContextHolder

number 2 接下来,运行应用程序。

与 xref:servlet/authentication/persistence.adoc#securitycontextpersistencefilter[SecurityContextPersistenceFilter 不同,SecurityContextHolderFilter 仅加载 SecurityContext,而不保存 SecurityContext。这意味着,在使用 SecurityContextHolderFilter 时,需要明确保存 SecurityContext

@10