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.
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.
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.
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.
-
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.
-
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
从 SecurityContextRepository
中加载 SecurityContext
并将其设置到 SecurityContextHolder
上。
Before running the rest of the application, SecurityContextPersistenceFilter
loads the SecurityContext
from the SecurityContextRepository
and sets it on the SecurityContextHolder
.
接下来,运行应用程序。
Next, the application is ran.
最后,如果 SecurityContext
已更改,则使用 SecurityContextPersistenceRepository
保存 SecurityContext
。这意味着在使用 SecurityContextPersistenceFilter
时,只需设置 SecurityContextHolder
,即可确保使用 SecurityContextRepository
对 SecurityContext
进行持久化。
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.
为了避免这些问题,SecurityContextPersistenceFilter
将 HttpServletRequest
和 HttpServletResponse
都同时封装,侦测 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
会从 SecurityContextRepository
加载 SecurityContext
并将其设定在 SecurityContextHolder
。
Before running the rest of the application, SecurityContextHolderFilter
loads the SecurityContext
from the SecurityContextRepository
and sets it on the SecurityContextHolder
.
接下来,运行应用程序。
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
-
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:
SecurityContextHolder
with SecurityContextPersistenceFilter
-
Java
-
Kotlin
SecurityContextHolder.setContext(securityContext);
SecurityContextHolder.setContext(securityContext)
应替换为
should be replaced with
SecurityContextHolder
with SecurityContextHolderFilter
-
Java
-
Kotlin
SecurityContextHolder.setContext(securityContext);
securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse);
SecurityContextHolder.setContext(securityContext)
securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse)