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.

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>

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)

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.

Configure SecurityContextRepository with 6.0 defaults
  • 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 HttpSessionSecurityContextRepository, you should replace it with your chosen implementation in the example above to ensure that it is used along with RequestAttributeSecurityContextRepository.

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:

Provide 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.

Require Explicit 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:

Explicit use Spring Security 5.8 defaults for 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>