Persisting Authentication
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:
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
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
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
In Spring Security the association of the user to future requests is made using SecurityContextRepository
.
The default implementation of SecurityContextRepository
is DelegatingSecurityContextRepository
which delegates to the following:
HttpSessionSecurityContextRepository
The 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
If it is not desirable to associate the SecurityContext
to an HttpSession
(i.e. when authenticating with OAuth) the NullSecurityContextRepository
is an implementation of SecurityContextRepository
that does nothing.
RequestAttributeSecurityContextRepository
The 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
.
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
The DelegatingSecurityContextRepository
saves the SecurityContext
to multiple SecurityContextRepository
delegates and allows retrieval from any of the delegates in a specified order.
The most useful arrangement for this is configured with the following example, which allows the use of both RequestAttributeSecurityContextRepository
and HttpSessionSecurityContextRepository
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>
In Spring Security 6, the example shown above is the default configuration. |
SecurityContextPersistenceFilter
The SecurityContextPersistenceFilter
is responsible for persisting the SecurityContext
between requests using the SecurityContextRepository
.
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.
Finally, if the SecurityContext
has changed, we save the SecurityContext
using the SecurityContextRepository
.
This means that when using SecurityContextPersistenceFilter
, just setting the SecurityContextHolder
will ensure that the SecurityContext
is persisted using SecurityContextRepository
.
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.
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
The SecurityContextHolderFilter
is responsible for loading the SecurityContext
between requests using the SecurityContextRepository
.
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.
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.
-
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>
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)