For the latest stable version, please use Spring Security 6.3.4! |
Session Management Migrations
Require Explicit Saving of SecurityContextRepository
In Spring Security 5, the default behavior is for the SecurityContext
to automatically be saved to the SecurityContextRepository
using the SecurityContextPersistenceFilter
.
Saving must be done just prior to the HttpServletResponse
being committed and just before SecurityContextPersistenceFilter
.
Unfortunately, automatic persistence of the SecurityContext
can surprise users when it is done prior to the request completing (i.e. just prior to committing the HttpServletResponse
).
It also is complex to keep track of the state to determine if a save is necessary causing unnecessary writes to the SecurityContextRepository
(i.e. HttpSession
) at times.
In Spring Security 6, the default behavior is that the SecurityContextHolderFilter
will only read the SecurityContext
from SecurityContextRepository
and populate it in the SecurityContextHolder
.
Users now must explicitly save the SecurityContext
with the SecurityContextRepository
if they want the SecurityContext
to persist between requests.
This removes ambiguity and improves performance by only requiring writing to the SecurityContextRepository
(i.e. HttpSession
) when it is necessary.
Saving the context is also needed when clearing it out, for example during logout. Refer to this section to know more about that. |
To opt into the new Spring Security 6 default, the following configuration can be used.
-
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)
Change HttpSessionSecurityContextRepository
to DelegatingSecurityContextRepository
In Spring Security 5, the default SecurityContextRepository
is HttpSessionSecurityContextRepository
.
In Spring Security 6, the default SecurityContextRepository
is DelegatingSecurityContextRepository
.
To opt into the new Spring Security 6 default, the following configuration can be used.
-
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>
If you are already using an implementation other than |
Address SecurityContextRepository
Deprecations
In Spring Security 5.7, a new method was added to SecurityContextRepository
with the signature:
Supplier<SecurityContext> loadContext(HttpServletRequest request)
With the addition of DelegatingSecurityContextRepository
in Spring Security 5.8, that method was deprecated in favor of a new method with the signature:
DeferredSecurityContext loadDeferredContext(HttpServletRequest request)
In Spring Security 6, the deprecated method was removed.
If you have implemented SecurityContextRepository
yourself and added an implementation of the loadContext(request)
method, you can prepare for Spring Security 6 by removing the implementation of that method and implementing the new method instead.
To get started implementing the new method, use the following example to provide a DeferredSecurityContext
:
DeferredSecurityContext
-
Java
-
Kotlin
@Override
public DeferredSecurityContext loadDeferredContext(HttpServletRequest request) {
return new DeferredSecurityContext() {
private SecurityContext securityContext;
private boolean isGenerated;
@Override
public SecurityContext get() {
if (this.securityContext == null) {
this.securityContext = getContextOrNull(request);
if (this.securityContext == null) {
SecurityContextHolderStrategy strategy = SecurityContextHolder.getContextHolderStrategy();
this.securityContext = strategy.createEmptyContext();
this.isGenerated = true;
}
}
return this.securityContext;
}
@Override
public boolean isGenerated() {
get();
return this.isGenerated;
}
};
}
override fun loadDeferredContext(request: HttpServletRequest): DeferredSecurityContext {
return object : DeferredSecurityContext {
private var securityContext: SecurityContext? = null
private var isGenerated = false
override fun get(): SecurityContext {
if (securityContext == null) {
securityContext = getContextOrNull(request)
?: SecurityContextHolder.getContextHolderStrategy().createEmptyContext()
.also { isGenerated = true }
}
return securityContext!!
}
override fun isGenerated(): Boolean {
get()
return isGenerated
}
}
}
Optimize Querying of RequestCache
In Spring Security 5, the default behavior is to query the saved request on every request.
This means that in a typical setup, that in order to use the RequestCache
the HttpSession
is queried on every request.
In Spring Security 6, the default is that RequestCache
will only be queried for a cached request if the HTTP parameter continue
is defined.
This allows Spring Security to avoid unnecessarily reading the HttpSession
with the RequestCache
.
In Spring Security 5 the default is to use HttpSessionRequestCache
which will be queried for a cached request on every request.
If you are not overriding the defaults (i.e. using NullRequestCache
), then the following configuration can be used to explicitly opt into the Spring Security 6 behavior in Spring Security 5.8:
RequestCache
Only Checks for Saved Requests if continue
Parameter Present-
Java
-
Kotlin
-
XML
@Bean
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
requestCache.setMatchingRequestParameterName("continue");
http
// ...
.requestCache((cache) -> cache
.requestCache(requestCache)
);
return http.build();
}
@Bean
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
val httpRequestCache = HttpSessionRequestCache()
httpRequestCache.setMatchingRequestParameterName("continue")
http {
requestCache {
requestCache = httpRequestCache
}
}
return http.build()
}
<http auto-config="true">
<!-- ... -->
<request-cache ref="requestCache"/>
</http>
<b:bean id="requestCache" class="org.springframework.security.web.savedrequest.HttpSessionRequestCache"
p:matchingRequestParameterName="continue"/>
Require Explicit Invocation of SessionAuthenticationStrategy
In Spring Security 5, the default configuration relies on SessionManagementFilter
to detect if a user just authenticated and invoke the SessionAuthenticationStrategy
.
The problem with this is that it means that in a typical setup, the HttpSession
must be read for every request.
In Spring Security 6, the default is that authentication mechanisms themselves must invoke the SessionAuthenticationStrategy
.
This means that there is no need to detect when Authentication
is done and thus the HttpSession
does not need to be read for every request.
To opt into the new Spring Security 6 default, the following configuration can be used.
SessionAuthenticationStrategy
Invocation-
Java
-
Kotlin
-
XML
@Bean
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
http
// ...
.sessionManagement((sessions) -> sessions
.requireExplicitAuthenticationStrategy(true)
);
return http.build();
}
@Bean
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
http {
sessionManagement {
requireExplicitAuthenticationStrategy = true
}
}
return http.build()
}
<http>
<!-- ... -->
<session-management authentication-strategy-explicit-invocation="true"/>
</http>
If this breaks your application, then you can explicitly opt into the 5.8 defaults using the following configuration:
SessionAuthenticationStrategy
-
Java
-
Kotlin
-
XML
@Bean
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
http
// ...
.sessionManagement((sessions) -> sessions
.requireExplicitAuthenticationStrategy(false)
);
return http.build();
}
@Bean
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
http {
sessionManagement {
requireExplicitAuthenticationStrategy = false
}
}
return http.build()
}
<http>
<!-- ... -->
<session-management authentication-strategy-explicit-invocation="false"/>
</http>