Persisting Authentication

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

The first time a user requests a protected resource, they are prompted for credentials. One of the most common ways to prompt for credentials is to redirect the user to a log in page. A summarized HTTP exchange for an unauthenticated user requesting a protected resource might look like this: .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

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

The user submits their username and password.

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

Upon authenticating the user, the user is associated to a new session id to prevent 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。

Subsequent requests include the session cookie which is used to authenticate the user for the remainder of the session.

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],它委托给以下内容:

In Spring Security the association of the user to future requests is made using {security-api-url}org/springframework/security/web/context/SecurityContextRepository.html[SecurityContextRepository]. The default implementation of SecurityContextRepository is {security-api-url}org/springframework/security/web/context/DelegatingSecurityContextRepository.html[DelegatingSecurityContextRepository] which delegates to the following:

  • <<`HttpSessionSecurityContextRepository`,httpsecuritycontextrepository>>

  • <<`RequestAttributeSecurityContextRepository`,requestattributesecuritycontextrepository>>

HttpSessionSecurityContextRepository

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

The {security-api-url}org/springframework/security/web/context/HttpSessionSecurityContextRepository.html[HttpSessionSecurityContextRepository] associates the SecurityContext to the HttpSession. Users can replace HttpSessionSecurityContextRepository with another implementation of SecurityContextRepository if they wish to associate the user with subsequent requests in another way or not at all.

NullSecurityContextRepository

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

If it is not desirable to associate the SecurityContext to an HttpSession (i.e. when authenticating with OAuth) the {security-api-url}org/springframework/security/web/context/NullSecurityContextRepository.html[NullSecurityContextRepository] is an implementation of SecurityContextRepository that does nothing.

RequestAttributeSecurityContextRepository

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

The {security-api-url}org/springframework/security/web/context/RequestAttributeSecurityContextRepository.html[RequestAttributeSecurityContextRepository] saves the SecurityContext as a request attribute to make sure the SecurityContext is available for a single request that occurs across dispatch types that may clear out the SecurityContext.

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

For example, assume that a client makes a request, is authenticated, and then an error occurs. Depending on the servlet container implementation, the error means that any SecurityContext that was established is cleared out and then the error dispatch is made. When the error dispatch is made, there is no SecurityContext established. This means that the error page cannot use the SecurityContext for authorization or displaying the current user unless the SecurityContext is persisted somehow.

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 委托,并允许按指定顺序从任何委托中检索。

The {security-api-url}org/springframework/security/web/context/DelegatingSecurityContextRepository.html[DelegatingSecurityContextRepository] saves the SecurityContext to multiple SecurityContextRepository delegates and allows retrieval from any of the delegates in a specified order.

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

The most useful arrangement for this is configured with the following example, which allows the use of both <<`RequestAttributeSecurityContextRepository`,requestattributesecuritycontextrepository>> and <<`HttpSessionSecurityContextRepository`,httpsecuritycontextrepository>> simultaneously.

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 中,上面显示的示例为默认配置。

In Spring Security 6, the example shown above is the default configuration.

SecurityContextPersistenceFilter

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

The {security-api-url}org/springframework/security/web/context/SecurityContextPersistenceFilter.html[SecurityContextPersistenceFilter] is responsible for persisting the SecurityContext between requests using the SecurityContextRepository.

securitycontextpersistencefilter

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

number 1 Before running the rest of the application, SecurityContextPersistenceFilter loads the SecurityContext from the SecurityContextRepository and sets it on the SecurityContextHolder.

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

number 2 Next, the application is ran.

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

number 3 Finally, if the SecurityContext has changed, we save the SecurityContext using the SecurityContextPersistenceRepository. This means that when using SecurityContextPersistenceFilter, just setting the SecurityContextHolder will ensure that the SecurityContext is persisted using SecurityContextRepository.

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

In some cases a response is committed and written to the client before the SecurityContextPersistenceFilter method completes. For example, if a redirect is sent to the client the response is immediately written back to the client. This means that establishing an HttpSession would not be possible in step 3 because the session id could not be included in the already written response. Another situation that can happen is that if a client authenticates successfully, the response is committed before SecurityContextPersistenceFilter completes, and the client makes a second request before the SecurityContextPersistenceFilter completes the wrong authentication could be present in the second request.

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

To avoid these problems, the SecurityContextPersistenceFilter wraps both the HttpServletRequest and the HttpServletResponse to detect if the SecurityContext has changed and if so save the SecurityContext just before the response is committed.

SecurityContextHolderFilter

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

The {security-api-url}org/springframework/security/web/context/SecurityContextHolderFilter.html[SecurityContextHolderFilter] is responsible for loading the SecurityContext between requests using the SecurityContextRepository.

securitycontextholderfilter

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

number 1 Before running the rest of the application, SecurityContextHolderFilter loads the SecurityContext from the SecurityContextRepository and sets it on the SecurityContextHolder.

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

number 2 Next, the application is ran.

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

Unlike, SecurityContextPersistenceFilter, SecurityContextHolderFilter only loads the SecurityContext it does not save the SecurityContext. This means that when using SecurityContextHolderFilter, it is required that the SecurityContext is explicitly saved.

@10

Explicit Saving of 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 中(如果它应该在请求之间持久化的话)。

Upon using the configuration, it is important that any code that sets the SecurityContextHolder with a SecurityContext also saves the SecurityContext to the SecurityContextRepository if it should be persisted between requests.

例如,以下代码:

For example, the following code:

Setting SecurityContextHolder with SecurityContextPersistenceFilter
  • Java

  • Kotlin

SecurityContextHolder.setContext(securityContext);
SecurityContextHolder.setContext(securityContext)

应替换为

should be replaced with

Setting SecurityContextHolder with SecurityContextHolderFilter
  • Java

  • Kotlin

SecurityContextHolder.setContext(securityContext);
securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse);
SecurityContextHolder.setContext(securityContext)
securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse)