diff --git a/docs/modules/ROOT/pages/servlet/architecture.adoc b/docs/modules/ROOT/pages/servlet/architecture.adoc
index c9a78d54c3..c9ce30d52f 100644
--- a/docs/modules/ROOT/pages/servlet/architecture.adoc
+++ b/docs/modules/ROOT/pages/servlet/architecture.adoc
@@ -268,6 +268,60 @@ The code below demonstrates how to customize the `RequestCache` implementation t
include::partial$servlet/architecture/request-cache-continue.adoc[]
+[[requestcache-prevent-saved-request]]
+==== Prevent the Request From Being Saved
+
+There are a number of reasons you may want to not store the user's unauthenticated request in the session.
+You may want to offload that storage onto the user's browser or store it in a database.
+Or you may want to shut off this feature since you always want to redirect the user to the home page instead of the page they tried to visit before login.
+
+To do that, you can use {security-api-url}org/springframework/security/web/savedrequest/NullRequestCache.html[the `NullRequestCache` implementation].
+
+.Prevent the Request From Being Saved
+====
+.Java
+[source,java,role="primary"]
+----
+@Bean
+SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
+ RequestCache nullRequestCache = new NullRequestCache();
+ http
+ // ...
+ .requestCache((cache) -> cache
+ .requestCache(nullRequestCache)
+ );
+ return http.build();
+}
+----
+
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+@Bean
+open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
+ val nullRequestCache = NullRequestCache()
+ http {
+ requestCache {
+ requestCache = nullRequestCache
+ }
+ }
+ return http.build()
+}
+----
+
+.XML
+[source,xml,role="secondary"]
+----
+
+
+
+
+
+
+----
+====
+
+
[[requestcacheawarefilter]]
=== RequestCacheAwareFilter
diff --git a/docs/modules/ROOT/pages/servlet/authentication/session-management.adoc b/docs/modules/ROOT/pages/servlet/authentication/session-management.adoc
index c5274ccb9b..80819a94a6 100644
--- a/docs/modules/ROOT/pages/servlet/authentication/session-management.adoc
+++ b/docs/modules/ROOT/pages/servlet/authentication/session-management.adoc
@@ -1,13 +1,10 @@
[[session-mgmt]]
-= Session Management
-HTTP session-related functionality is handled by a combination of the {security-api-url}org/springframework/security/authentication/AuthenticationProvider.html[`SessionManagementFilter`] and the {security-api-url}org/springframework/security/web/authentication/session/SessionAuthenticationStrategy.html[`SessionAuthenticationStrategy`] interface, to which the filter delegates.
-Typical usage includes session-fixation protection attack prevention, detection of session timeouts, and restrictions on how many sessions an authenticated user may have open concurrently.
+= Authentication Persistence and Session Management
-[[session-mgmt-force-session-creation]]
-== Force Eager Session Creation
+Once you have got an application that is xref:servlet/authentication/index.adoc[authenticating requests], it is important to consider how that resulting authentication will be persisted and restored on future requests.
-At times it can be valuable to eagerly create sessions.
-This can be done by using the {security-api-url}org/springframework/security/web/session/ForceEagerSessionCreationFilter.html[`ForceEagerSessionCreationFilter`] which can be configured using:
+This is done automatically by default, so no additional code is necessary, though there are some steps you should consider. The first is setting the `requireExplicitSave` property in `HttpSecurity`.
+You can do it like so:
====
.Java
@@ -16,25 +13,405 @@ This can be done by using the {security-api-url}org/springframework/security/web
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
http
- .sessionManagement(session -> session
- .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
+ // ...
+ .securityContext((context) -> context
+ .requireExplicitSave(true)
);
return http.build();
}
----
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+@Bean
+open fun filterChain(http: HttpSecurity): SecurityFilterChain {
+ http {
+ // ...
+ securityContext {
+ requireExplicitSave = true
+ }
+ }
+ return http.build()
+}
+----
+
.XML
[source,xml,role="secondary"]
----
-
-
+
+
----
====
-== Detecting Timeouts
-You can configure Spring Security to detect the submission of an invalid session ID and redirect the user to an appropriate URL.
-To do so, configure the `session-management` element:
+The most straightforward reason for this is that it is xref:migration/servlet/session-management.adoc#_require_explicit_saving_of_securitycontextrepository[becoming the default value in 6.0], so this will make sure you are ready for that.
+
+If you like, <> or <>. Otherwise, in most cases you are done with this section.
+
+But before you leave, consider if any of these use cases fit your application:
+
+* I want to <>
+* I want to <> a user can be logged in concurrently
+* I want <> myself instead of Spring Security doing it for me
+* I am using <> and I need <>
+* I want to store the authentication <>
+* I am using a <>, but <>
+* I am using `SessionCreationPolicy.NEVER` but <>.
+
+
+[[understanding-session-management-components]]
+== Understanding Session Management's Components
+
+The Session Management support is composed of a few components that work together to provide the functionality.
+Those components are, xref:servlet/authentication/persistence.adoc#securitycontextholderfilter[the `SecurityContextHolderFilter`], xref:servlet/authentication/persistence.adoc#securitycontextpersistencefilter[the `SecurityContextPersistenceFilter`] and <>.
+
+[NOTE]
+=====
+In Spring Security 6, the `SecurityContextPersistenceFilter` and `SessionManagementFilter` are not set by default.
+In addition to that, any application should only have either `SecurityContextHolderFilter` or `SecurityContextPersistenceFilter` set, never both.
+=====
+
+[[the-sessionmanagementfilter]]
+=== The `SessionManagementFilter`
+
+The `SessionManagementFilter` checks the contents of the `SecurityContextRepository` against the current contents of the `SecurityContextHolder` to determine whether a user has been authenticated during the current request, typically by a non-interactive authentication mechanism, such as pre-authentication or remember-me footnote:[
+Authentication by mechanisms which perform a redirect after authenticating (such as form-login) will not be detected by `SessionManagementFilter`, as the filter will not be invoked during the authenticating request.
+Session-management functionality has to be handled separately in these cases.
+].
+If the repository contains a security context, the filter does nothing.
+If it doesn't, and the thread-local `SecurityContext` contains a (non-anonymous) `Authentication` object, the filter assumes they have been authenticated by a previous filter in the stack.
+It will then invoke the configured `SessionAuthenticationStrategy`.
+
+If the user is not currently authenticated, the filter will check whether an invalid session ID has been requested (because of a timeout, for example) and will invoke the configured `InvalidSessionStrategy`, if one is set.
+The most common behaviour is just to redirect to a fixed URL and this is encapsulated in the standard implementation `SimpleRedirectInvalidSessionStrategy`.
+The latter is also used when configuring an invalid session URL through the namespace, <>.
+
+[[moving-away-from-sessionmanagementfilter]]
+==== Moving Away From `SessionManagementFilter`
+
+In Spring Security 5, the default configuration relies on `SessionManagementFilter` to detect if a user just authenticated and invoke <<_the_sessionauthenticationstrategy,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 should be used.
+
+.Require Explicit `SessionAuthenticationStrategy` Invocation
+====
+.Java
+[source,java,role="primary"]
+----
+@Bean
+SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
+ http
+ // ...
+ .sessionManagement((sessions) -> sessions
+ .requireExplicitAuthenticationStrategy(true)
+ );
+ return http.build();
+}
+----
+
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+@Bean
+open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
+ http {
+ sessionManagement {
+ requireExplicitAuthenticationStrategy = true
+ }
+ }
+ return http.build()
+}
+----
+
+.XML
+[source,xml,role="secondary"]
+----
+
+
+
+
+----
+====
+
+==== Things To Consider When Moving Away From `SessionManagementFilter`
+
+When `requireExplicitAuthenticationStrategy = true`, it means that the `SessionManagementFilter` will not be used, therefore, some methods from the `sessionManagement` DSL will not have any effect.
+
+|===
+|Method |Replacement
+
+|`sessionAuthenticationErrorUrl`
+|Configure an {security-api-url}/org/springframework/security/web/authentication/AuthenticationFailureHandler.html[`AuthenticationFailureHandler`] in your authentication mechanism
+
+|`sessionAuthenticationFailureHandler`
+|Configure an {security-api-url}/org/springframework/security/web/authentication/AuthenticationFailureHandler.html[`AuthenticationFailureHandler`] in your authentication mechanism
+
+|`sessionAuthenticationStrategy`
+|Configure an `SessionAuthenticationStrategy` in your authentication mechanism as <>
+|===
+
+In Spring Security 6, if you try to use any of these methods when `requireExplicitAuthenticationStrategy = true` (the default), an exception will be thrown.
+
+
+[[customizing-where-authentication-is-stored]]
+== Customizing Where the Authentication Is Stored
+
+By default, Spring Security stores the security context for you in the HTTP session (link to earlier description). However, here are several reasons you may want to customize that:
+
+* You may want call individual setters on the `HttpSessionSecurityContextRepository` instance
+* You may want to store the security context in a cache or database to enable horizontal scaling
+
+First, you need to create an implementation of `SecurityContextRepository` or use an existing implementation like `HttpSessionSecurityContextRepository`, then you can set it in `HttpSecurity`.
+
+[NOTE]
+====
+The above configuration sets the `SecurityContextRepository` on the `SecurityContextHolderFilter` and **participating** authentication filters, like `UsernamePasswordAuthenticationFilter`.
+To also set it in stateless filters, please see <>.
+====
+
+[[customizing-the-securitycontextrepository]]
+.Customizing the `SecurityContextRepository`
+====
+.Java
+[source,java,role="primary"]
+----
+@Bean
+public SecurityFilterChain filterChain(HttpSecurity http) {
+ SecurityContextRepository repo = new MyCustomSecurityContextRepository();
+ http
+ // ...
+ .securityContext((context) -> context
+ .requireExplicitSave(true)
+ .securityContextRepository(repo)
+ );
+ return http.build();
+}
+----
+
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+@Bean
+open fun filterChain(http: HttpSecurity): SecurityFilterChain {
+ val repo = MyCustomSecurityContextRepository()
+ http {
+ // ...
+ securityContext {
+ requireExplicitSave = true
+ securityContextRepository = repo
+ }
+ }
+ return http.build()
+}
+----
+
+.XML
+[source,xml,role="secondary"]
+----
+
+
+
+
+----
+====
+
+If you are using a custom authentication mechanism, you might want to <>.
+
+[[store-authentication-manually]]
+=== Storing the `Authentication` manually
+
+In some cases, for example, you might be authenticating a user manually instead of relying on Spring Security filters.
+You can use a custom filters or a {spring-framework-reference-url}/web.html#mvc-controller[Spring MVC controller] endpoint to do that.
+If you want to save the authentication between requests, in the `HttpSession`, for example, you have to do so:
+
+====
+.Java
+[source,java,role="primary"]
+----
+private SecurityContextRepository securityContextRepository =
+ new HttpSessionSecurityContextRepository(); <1>
+
+@PostMapping("/login")
+public void login(@RequestBody LoginRequest loginRequest, HttpServletRequest request, HttpServletResponse response) { <2>
+ UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.unauthenticated(
+ loginRequest.getUsername(), loginRequest.getPassword()); <3>
+ Authentication authentication = authenticationManager.authenticate(token); <4>
+ SecurityContext context = securityContextHolderStrategy.createEmptyContext();
+ context.setAuthentication(authentication); <5>
+ securityContextHolderStrategy.setContext(authentication);
+ securityContextRepository.saveContext(context, request, response); <6>
+}
+
+class LoginRequest {
+
+ private String username;
+ private String password;
+
+ // getters and setters
+}
+----
+====
+
+<1> Add the `SecurityContextRepository` to the controller
+<2> Inject the `HttpServletRequest` and `HttpServletResponse` to be able to save the `SecurityContext`
+<3> Create an unauthenticated `UsernamePasswordAuthenticationToken` using the provided credentials
+<4> Call `AuthenticationManager#authenticate` to authenticate the user
+<5> Create a `SecurityContext` and set the `Authentication` in it
+<6> Save the `SecurityContext` in the `SecurityContextRepository`
+
+And that's it.
+If you are not sure what `securityContextHolderStrategy` is in the above example, you can read more about it in the <>.
+
+[[stateless-authentication]]
+=== Configuring Persistence for Stateless Authentication
+
+Sometimes there is no need to create and maintain a `HttpSession` for example, to persist the authentication across requests.
+Some authentication mechanisms like xref:servlet/authentication/passwords/basic.adoc[HTTP Basic] are stateless and, therefore, re-authenticates the user on every request.
+
+If you do not wish to create sessions, you can use `SessionCreationPolicy.STATELESS`, like so:
+
+====
+.Java
+[source,java,role="primary"]
+----
+@Bean
+public SecurityFilterChain filterChain(HttpSecurity http) {
+ http
+ // ...
+ .sessionManagement((session) -> session
+ .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+ );
+ return http.build();
+}
+----
+
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+@Bean
+open fun filterChain(http: HttpSecurity): SecurityFilterChain {
+ http {
+ // ...
+ sessionManagement {
+ sessionCreationPolicy = SessionCreationPolicy.STATELESS
+ }
+ }
+ return http.build()
+}
+----
+
+.XML
+[source,xml,role="secondary"]
+----
+
+
+
+----
+====
+
+The above configuration is <> to use a `NullSecurityContextRepository` and is also xref:servlet/architecture.adoc#requestcache-prevent-saved-request[preventing the request from being saved in the session].
+
+[[never-policy-session-still-created]]
+
+If you are using `SessionCreationPolicy.NEVER`, you might notice that the application is still creating a `HttpSession`.
+In most cases, this happens because the xref:servlet/architecture.adoc#savedrequests[request is saved in the session] for the authenticated resource to re-request after authentication is successful.
+To avoid that, please refer to xref:servlet/architecture.adoc#requestcache-prevent-saved-request[how to prevent the request of being saved] section.
+
+
+[[storing-stateless-authentication-in-the-session]]
+==== Storing Stateless Authentication in the Session
+
+If, for some reason, you are using a stateless authentication mechanism, but you still want to store the authentication in the session you can use the `HttpSessionSecurityContextRepository` instead of the `NullSecurityContextRepository`.
+
+For the xref:servlet/authentication/passwords/basic.adoc[HTTP Basic], you can add xref:servlet/configuration/java.adoc#post-processing-configured-objects[a `ObjectPostProcessor`] that changes the `SecurityContextRepository` used by the `BasicAuthenticationFilter`:
+
+.Store HTTP Basic authentication in the `HttpSession`
+====
+.Java
+[source,java,role="primary"]
+----
+@Bean
+SecurityFilterChain web(HttpSecurity http) throws Exception {
+ http
+ // ...
+ .httpBasic((basic) -> basic
+ .addObjectPostProcessor(new ObjectPostProcessor() {
+ @Override
+ public O postProcess(O filter) {
+ filter.setSecurityContextRepository(new HttpSessionSecurityContextRepository());
+ return filter;
+ }
+ })
+ );
+
+ return http.build();
+}
+----
+====
+
+The above also applies to others authentication mechanisms, like xref:servlet/oauth2/resource-server/index.adoc[Bearer Token Authentication].
+
+
+[[requireexplicitsave]]
+== Understanding Require Explicit Save
+
+In Spring Security 5, the default behavior is for the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontext[`SecurityContext`] to automatically be saved to the xref:servlet/authentication/persistence.adoc#securitycontextrepository[`SecurityContextRepository`] using the <>.
+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.
+
+For these reasons, the `SecurityContextPersistenceFilter` has been deprecated to be replaced with the `SecurityContextHolderFilter`.
+In Spring Security 6, the default behavior is that xref:servlet/authentication/persistence.adoc#securitycontextholderfilter[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.
+
+[[how-it-works-requireexplicitsave]]
+=== How it works
+
+In summary, when `requireExplicitSave` is `true`, Spring Security sets up xref:servlet/authentication/persistence.adoc#securitycontextholderfilter[the `SecurityContextHolderFilter`] instead of xref:servlet/authentication/persistence.adoc#securitycontextpersistencefilter[the `SecurityContextPersistenceFilter`]
+
+
+[[ns-concurrent-sessions]]
+== Configuring Concurrent Session Control
+If you wish to place constraints on a single user's ability to log in to your application, Spring Security supports this out of the box with the following simple additions.
+First, you need to add the following listener to your configuration to keep Spring Security updated about session lifecycle events:
+
+====
+.Java
+[source,java,role="primary"]
+----
+@Bean
+public HttpSessionEventPublisher httpSessionEventPublisher() {
+ return new HttpSessionEventPublisher();
+}
+----
+
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+@Bean
+open fun httpSessionEventPublisher(): HttpSessionEventPublisher {
+ return HttpSessionEventPublisher()
+}
+----
+
+.web.xml
+[source,xml,role="secondary"]
+----
+
+
+ org.springframework.security.web.session.HttpSessionEventPublisher
+
+
+----
+====
+
+Then add the following lines to your security configuration:
====
.Java
@@ -44,25 +421,319 @@ To do so, configure the `session-management` element:
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.sessionManagement(session -> session
- .invalidSessionUrl("/invalidSession.htm")
+ .maximumSessions(1)
);
return http.build();
}
----
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+@Bean
+open fun filterChain(http: HttpSecurity): SecurityFilterChain {
+ http {
+ sessionManagement {
+ sessionConcurrency {
+ maximumSessions = 1
+ }
+ }
+ }
+ return http.build()
+}
+----
+
.XML
[source,xml,role="secondary"]
----
...
-
+
+
+
----
====
-Note that, if you use this mechanism to detect session timeouts, it may falsely report an error if the user logs out and then logs back in without closing the browser.
-This is because the session cookie is not cleared when you invalidate the session and is resubmitted even if the user has logged out.
-You may be able to explicitly delete the `JSESSIONID` cookie on logging out -- for example, by using the following syntax in the logout handler:
+
+This will prevent a user from logging in multiple times - a second login will cause the first to be invalidated.
+
+Using Spring Boot, you can test the above configuration scenario the following way:
+
+====
+.Java
+[source,java,role="primary"]
+----
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@AutoConfigureMockMvc
+public class MaximumSessionsTests {
+
+ @Autowired
+ private MockMvc mvc;
+
+ @Test
+ void loginOnSecondLoginThenFirstSessionTerminated() throws Exception {
+ MvcResult mvcResult = this.mvc.perform(formLogin())
+ .andExpect(authenticated())
+ .andReturn();
+
+ MockHttpSession firstLoginSession = (MockHttpSession) mvcResult.getRequest().getSession();
+
+ this.mvc.perform(get("/").session(firstLoginSession))
+ .andExpect(authenticated());
+
+ this.mvc.perform(formLogin()).andExpect(authenticated());
+
+ // first session is terminated by second login
+ this.mvc.perform(get("/").session(firstLoginSession))
+ .andExpect(unauthenticated());
+ }
+
+}
+----
+====
+
+You can try it using the {gh-samples-url}/servlet/spring-boot/java/session-management/maximum-sessions[Maximum Sessions sample].
+
+It is also common that you would prefer to prevent a second login, in which case you can use:
+
+====
+.Java
+[source,java,role="primary"]
+----
+@Bean
+public SecurityFilterChain filterChain(HttpSecurity http) {
+ http
+ .sessionManagement(session -> session
+ .maximumSessions(1)
+ .maxSessionsPreventsLogin(true)
+ );
+ return http.build();
+}
+----
+
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+@Bean
+open fun filterChain(http: HttpSecurity): SecurityFilterChain {
+ http {
+ sessionManagement {
+ sessionConcurrency {
+ maximumSessions = 1
+ maxSessionsPreventsLogin = true
+ }
+ }
+ }
+ return http.build()
+}
+----
+
+.XML
+[source,xml,role="secondary"]
+----
+
+
+
+
+
+----
+====
+
+
+The second login will then be rejected.
+By "rejected", we mean that the user will be sent to the `authentication-failure-url` if form-based login is being used.
+If the second authentication takes place through another non-interactive mechanism, such as "remember-me", an "unauthorized" (401) error will be sent to the client.
+If instead you want to use an error page, you can add the attribute `session-authentication-error-url` to the `session-management` element.
+
+Using Spring Boot, you can test the above configuration the following way:
+
+====
+.Java
+[source,java,role="primary"]
+----
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@AutoConfigureMockMvc
+public class MaximumSessionsPreventLoginTests {
+
+ @Autowired
+ private MockMvc mvc;
+
+ @Test
+ void loginOnSecondLoginThenPreventLogin() throws Exception {
+ MvcResult mvcResult = this.mvc.perform(formLogin())
+ .andExpect(authenticated())
+ .andReturn();
+
+ MockHttpSession firstLoginSession = (MockHttpSession) mvcResult.getRequest().getSession();
+
+ this.mvc.perform(get("/").session(firstLoginSession))
+ .andExpect(authenticated());
+
+ // second login is prevented
+ this.mvc.perform(formLogin()).andExpect(unauthenticated());
+
+ // first session is still valid
+ this.mvc.perform(get("/").session(firstLoginSession))
+ .andExpect(authenticated());
+ }
+
+}
+----
+====
+
+If you are using a customized authentication filter for form-based login, then you have to configure concurrent session control support explicitly.
+You can try it using the {gh-samples-url}/servlet/spring-boot/java/session-management/maximum-sessions-prevent-login[Maximum Sessions Prevent Login sample].
+
+== Detecting Timeouts
+
+Sessions expire on their own, and there is nothing that needs to be done to ensure that a security context gets removed.
+That said, Spring Security can detect when a session has expired and take specific actions that you indicate.
+For example, you may want to redirect to a specific endpoint when a user makes a request with an already-expired session.
+This is achieved through the `invalidSessionUrl` in `HttpSecurity`:
+
+====
+.Java
+[source,java,role="primary"]
+----
+@Bean
+public SecurityFilterChain filterChain(HttpSecurity http) {
+ http
+ .sessionManagement(session -> session
+ .invalidSessionUrl("/invalidSession")
+ );
+ return http.build();
+}
+----
+
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+@Bean
+open fun filterChain(http: HttpSecurity): SecurityFilterChain {
+ http {
+ sessionManagement {
+ invalidSessionUrl = "/invalidSession"
+ }
+ }
+ return http.build()
+}
+----
+
+.XML
+[source,xml,role="secondary"]
+----
+
+...
+
+
+----
+====
+
+Note that if you use this mechanism to detect session timeouts, it may falsely report an error if the user logs out and then logs back in without closing the browser.
+This is because the session cookie is not cleared when you invalidate the session and will be resubmitted even if the user has logged out.
+If that is your case, you might want to <>.
+
+=== Customizing the Invalid Session Strategy
+
+The `invalidSessionUrl` is a convenience method for setting the `InvalidSessionStrategy` using the {security-api-url}/org/springframework/security/web/session/SimpleRedirectInvalidSessionStrategy.html[`SimpleRedirectInvalidSessionStrategy` implementation].
+If you want to customize the behavior, you can implement the {security-api-url}/org/springframework/security/web/session/InvalidSessionStrategy.html[`InvalidSessionStrategy`] interface and configure it using the `invalidSessionStrategy` method:
+
+====
+.Java
+[source,java,role="primary"]
+----
+@Bean
+public SecurityFilterChain filterChain(HttpSecurity http) {
+ http
+ .sessionManagement(session -> session
+ .invalidSessionStrategy(new MyCustomInvalidSessionStrategy())
+ );
+ return http.build();
+}
+----
+
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+@Bean
+open fun filterChain(http: HttpSecurity): SecurityFilterChain {
+ http {
+ sessionManagement {
+ invalidSessionStrategy = MyCustomInvalidSessionStrategy()
+ }
+ }
+ return http.build()
+}
+----
+
+.XML
+[source,xml,role="secondary"]
+----
+
+...
+
+
+
+----
+====
+
+[[clearing-session-cookie-on-logout]]
+== Clearing Session Cookies on Logout
+
+You can explicitly delete the JSESSIONID cookie on logging out, for example by using the https://w3c.github.io/webappsec-clear-site-data/[`Clear-Site-Data` header] in the logout handler:
+
+====
+.Java
+[source,java,role="primary"]
+----
+@Bean
+public SecurityFilterChain filterChain(HttpSecurity http) {
+ http
+ .logout((logout) -> logout
+ .addLogoutHandler(new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(COOKIES)))
+ );
+ return http.build();
+}
+----
+
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+@Bean
+open fun filterChain(http: HttpSecurity): SecurityFilterChain {
+ http {
+ logout {
+ addLogoutHandler(HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter(COOKIES)))
+ }
+ }
+ return http.build()
+}
+----
+
+.XML
+[source,xml,role="secondary"]
+----
+
+
+
+
+
+
+
+
+----
+====
+
+This has the advantage of being container agnostic and will work with any container that supports the `Clear-Site-Data` header.
+
+As an alternative, you can also use the following syntax in the logout handler:
====
.Java
@@ -78,16 +749,29 @@ public SecurityFilterChain filterChain(HttpSecurity http) {
}
----
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+@Bean
+open fun filterChain(http: HttpSecurity): SecurityFilterChain {
+ http {
+ logout {
+ deleteCookies("JSESSIONID")
+ }
+ }
+ return http.build()
+}
+----
+
.XML
[source,xml,role="secondary"]
----
-
+
----
====
-
Unfortunately, this cannot be guaranteed to work with every servlet container, so you need to test it in your environment.
[NOTE]
@@ -105,270 +789,185 @@ Header always set Set-Cookie "JSESSIONID=;Path=/tutorial;Expires=Thu, 01 Jan 197
----
====
+More details on the xref:servlet/exploits/headers.adoc#servlet-headers-clear-site-data[Clear Site Data] and xref:servlet/authentication/logout.adoc[Logout sections].
-[[ns-concurrent-sessions]]
-== Concurrent Session Control
-If you wish to place constraints on a single user's ability to log in to your application, Spring Security supports this with the following simple additions.
-First, you need to add the following listener to your `web.xml` file to keep Spring Security updated about session lifecycle events:
-====
-.Java
-[source,java,role="primary"]
-----
-@Bean
-public HttpSessionEventPublisher httpSessionEventPublisher() {
- return new HttpSessionEventPublisher();
-}
-----
-
-.XML
-[source,xml,role="secondary"]
-----
-
-
- org.springframework.security.web.session.HttpSessionEventPublisher
-
-
-----
-====
-
-Then add the following lines to your application context:
-
-====
-.Java
-[source,java,role="primary"]
-----
-@Bean
-public SecurityFilterChain filterChain(HttpSecurity http) {
- http
- .sessionManagement(session -> session
- .maximumSessions(1)
- );
- return http.build();
-}
-----
-
-.XML
-[source,xml,role="secondary"]
-----
-
-...
-
-
-
-
-----
-====
-
-These changes prevent a user from logging in multiple times. A second login causes the first to be invalidated.
-Often, you would prefer to prevent a second login. In that case, you can use:
-
-====
-.Java
-[source,java,role="primary"]
-----
-@Bean
-public SecurityFilterChain filterChain(HttpSecurity http) {
- http
- .sessionManagement(session -> session
- .maximumSessions(1)
- .maxSessionsPreventsLogin(true)
- );
- return http.build();
-}
-----
-
-.XML
-[source,xml,role="secondary"]
-----
-
-
-
-
-
-----
-====
-
-The second login is then rejected.
-By "`rejected`", we mean that the user is sent to the `authentication-failure-url` if form-based login is being used.
-If the second authentication takes place through another non-interactive mechanism, such as "`remember-me`", an "`unauthorized`" (401) error is sent to the client.
-If, instead, you want to use an error page, you can add the `session-authentication-error-url` attribute to the `session-management` element.
-
-If you use a customized authentication filter for form-based login, you have to configure concurrent session control support explicitly.
-You can find more details in the <>.
[[ns-session-fixation]]
-== Session Fixation Attack Protection
-https://en.wikipedia.org/wiki/Session_fixation[Session fixation] attacks are a potential risk where it is possible for a malicious attacker to create a session by accessing a site and then persuade another user to log in with the same session (by sending them a link containing the session identifier as a parameter, for example).
-Spring Security automatically protects against this by creating a new session or otherwise changing the session ID when a user logs in.
-If you do not require this protection or it conflicts with some other requirement, you can control the behavior setting the `session-fixation-protection` attribute on ``, which has four options
+== Understanding Session Fixation Attack Protection
-* `none`: Do nothing.
-The original session is retained.
+https://en.wikipedia.org/wiki/Session_fixation[Session fixation] attacks are a potential risk where it is possible for a malicious attacker to create a session by accessing a site, then persuade another user to log in with the same session (by sending them a link containing the session identifier as a parameter, for example).
+Spring Security protects against this automatically by creating a new session or otherwise changing the session ID when a user logs in.
-* `newSession`: Create a new, "`clean`" session, without copying the existing session data (Spring Security-related attributes are still copied).
+=== Configuring Session Fixation Protection
-* `migrateSession`: Create a new session and copy all existing session attributes to the new session.
+You can control the strategy for Session Fixation Protection by choosing between three recommended options:
+
+* `changeSessionId` - Do not create a new session.
+Instead, use the session fixation protection provided by the Servlet container (`HttpServletRequest#changeSessionId()`).
+This option is only available in Servlet 3.1 (Java EE 7) and newer containers.
+Specifying it in older containers will result in an exception.
+This is the default in Servlet 3.1 and newer containers.
+
+* `newSession` - Create a new "clean" session, without copying the existing session data (Spring Security-related attributes will still be copied).
+
+* `migrateSession` - Create a new session and copy all existing session attributes to the new session.
This is the default in Servlet 3.0 or older containers.
-* `changeSessionId`: Do not create a new session.
-Instead, use the session fixation protection provided by the Servlet container (`HttpServletRequest#changeSessionId()`).
-This option is available only in Servlet 3.1 (Java EE 7) and newer containers, where it is the default.
-Specifying it in older containers results in an exception.
+You can configure the session fixation protection by doing:
+
+====
+.Java
+[source,java,role="primary"]
+----
+@Bean
+public SecurityFilterChain filterChain(HttpSecurity http) {
+ http
+ .sessionManagement((session) - session
+ .sessionFixation((sessionFixation) -> sessionFixation
+ .newSession()
+ )
+ );
+ return http.build();
+}
+----
+
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+@Bean
+open fun filterChain(http: HttpSecurity): SecurityFilterChain {
+ http {
+ sessionManagement {
+ sessionFixation {
+ newSession()
+ }
+ }
+ }
+ return http.build()
+}
+----
+
+.XML
+[source,xml,role="secondary"]
+----
+
+
+
+----
+====
When session fixation protection occurs, it results in a `SessionFixationProtectionEvent` being published in the application context.
-If you use `changeSessionId`, this protection will _also_ result in any `javax.servlet.http.HttpSessionIdListener` instances being notified, so use caution if your code listens for both events.
-See the <> chapter for additional information.
+If you use `changeSessionId`, this protection will __also__ result in any ``jakarta.servlet.http.HttpSessionIdListener``s being notified, so use caution if your code listens for both events.
-== SessionManagementFilter
-TThe `SessionManagementFilter` checks the contents of the `SecurityContextRepository` against the current contents of the `SecurityContextHolder` to determine whether a user has been authenticated during the current request, typically by a non-interactive authentication mechanism, such as pre-authentication or remember-me
-
-[NOTE]
-====
-Authentication by mechanisms that perform a redirect after authenticating (such as form-login) are not detected by `SessionManagementFilter`, as the filter is not invoked during the authenticating request.
-Session-management functionality has to be handled separately in these cases.
-====
-
-If the repository contains a security context, the filter does nothing.
-If it does not and the thread-local `SecurityContext` contains a (non-anonymous) `Authentication` object, the filter assumes they have been authenticated by a previous filter in the stack.
-It then invokes the configured `SessionAuthenticationStrategy`.
-
-If the user is not currently authenticated, the filter will check whether an invalid session ID has been requested (because of a timeout, for example) and will invoke the configured `InvalidSessionStrategy`, if one is set.
-The most common behaviour is just to redirect to a fixed URL and this is encapsulated in the standard implementation `SimpleRedirectInvalidSessionStrategy`.
-The latter is also used when configuring an invalid session URL through the namespace, <>.
+You can also set the session fixation protection to `none` to disable it, but this is not recommended as it leaves your application vulnerable.
-== SessionAuthenticationStrategy
-`SessionAuthenticationStrategy` is used by both `SessionManagementFilter` and `AbstractAuthenticationProcessingFilter`, so, if you are using a customized form-login class, for example, you need to inject it into both of these.
-In this case, a typical configuration that combines the namespace and custom beans might look like this:
+
+[[use-securitycontextholderstrategy]]
+== Using `SecurityContextHolderStrategy`
+
+Consider the following block of code:
====
-[source,xml]
+.Java
+[source,java,role="primary"]
----
-
-
-
+UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
+ loginRequest.getUsername(), loginRequest.getPassword());
+Authentication authentication = this.authenticationManager.authenticate(token);
+// ...
+SecurityContext context = SecurityContextHolder.createEmptyContext(); <1>
+context.setAuthentication(authentication); <2>
+SecurityContextHolder.setContext(authentication); <3>
+----
+====
+
+1. Creates an empty `SecurityContext` instance by accessing the `SecurityContextHolder` statically.
+2. Sets the `Authentication` object in the `SecurityContext` instance.
+3. Sets the `SecurityContext` instance in the `SecurityContextHolder` statically.
+
+While the above code works fine, it can produce some undesired effects: when components access the `SecurityContext` statically through `SecurityContextHolder`, this can create race conditions when there are multiple application contexts that want to specify the `SecurityContextHolderStrategy`.
+This is because in `SecurityContextHolder` there is one strategy per classloader instead of one per application context.
+
+To address this, components can wire `SecurityContextHolderStrategy` from the application context.
+By default, they will still look up the strategy from `SecurityContextHolder`.
+
+These changes are largely internal, but they present the opportunity for applications to autowire the `SecurityContextHolderStrategy` instead of accessing the `SecurityContext` statically.
+To do so, you should change the code to the following:
+
+====
+.Java
+[source,java,role="primary"]
+----
+public class SomeClass {
+
+ private final SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();
+
+ public void someMethod() {
+ UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.unauthenticated(
+ loginRequest.getUsername(), loginRequest.getPassword());
+ Authentication authentication = this.authenticationManager.authenticate(token);
+ // ...
+ SecurityContext context = this.securityContextHolderStrategy.createEmptyContext(); <1>
+ context.setAuthentication(authentication); <2>
+ this.securityContextHolderStrategy.setContext(authentication); <3>
+ }
+
+}
+----
+====
+
+1. Creates an empty `SecurityContext` instance using the configured `SecurityContextHolderStrategy`.
+2. Sets the `Authentication` object in the `SecurityContext` instance.
+3. Sets the `SecurityContext` instance in the `SecurityContextHolderStrategy`.
+
+
+[[session-mgmt-force-session-creation]]
+== Forcing Eager Session Creation
+
+At times, it can be valuable to eagerly create sessions.
+This can be done by using the {security-api-url}org/springframework/security/web/session/ForceEagerSessionCreationFilter.html[`ForceEagerSessionCreationFilter`] which can be configured using:
+
+====
+.Java
+[source,java,role="primary"]
+----
+@Bean
+public SecurityFilterChain filterChain(HttpSecurity http) {
+ http
+ .sessionManagement(session -> session
+ .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
+ );
+ return http.build();
+}
+----
+
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+@Bean
+open fun filterChain(http: HttpSecurity): SecurityFilterChain {
+ http {
+ sessionManagement {
+ sessionCreationPolicy = SessionCreationPolicy.ALWAYS
+ }
+ }
+ return http.build()
+}
+----
+
+.XML
+[source,xml,role="secondary"]
+----
+
+
-
-
-
- ...
-
-
-
-----
-====
-
-Note that the use of the default, `SessionFixationProtectionStrategy`, may cause issues if you are storing beans in the session that implement `HttpSessionBindingListener`, including Spring session-scoped beans.
-See the Javadoc for this Java class for more information.
-
-[[concurrent-sessions]]
-== Concurrency Control
-Spring Security can prevent a principal from concurrently authenticating to the same application more than a specified number of times.
-Many ISVs take advantage of this to enforce licensing, while network administrators like this feature because it helps prevent people from sharing login names.
-You can, for example, stop user `Batman` from logging onto the web application from two different sessions.
-You can either expire their previous login or you can report an error when they try to log in again, preventing the second login.
-Note that, if you use the second approach, a user who has not explicitly logged out (but who has just closed their browser, for example) cannot log in again until their original session expires.
-
-//FIXME: Add a link to the namespace chapter.
-Concurrency control is supported by the namespace, so please check the earlier namespace chapter for the simplest configuration.
-Sometimes, though, you need to customize things.
-
-The implementation uses a specialized version of `SessionAuthenticationStrategy`, called `ConcurrentSessionControlAuthenticationStrategy`.
-
-[NOTE]
-====
-Previously, the concurrent authentication check was made by the `ProviderManager`, which could be injected with a `ConcurrentSessionController`.
-The latter would check if the user was attempting to exceed the number of permitted sessions.
-However, this approach required that an HTTP session be created in advance, which is undesirable.
-In Spring Security 3 and later, the user is first authenticated by the `AuthenticationManager` and once they are successfully authenticated, a session is created and the check is made whether they are allowed to have another session open.
-====
-
-To use concurrent session support, you need to add the following to `web.xml`:
-
-====
-[source,xml]
-----
-
-
- org.springframework.security.web.session.HttpSessionEventPublisher
-
-
-----
-====
-
-In addition, you need to add the `ConcurrentSessionFilter` to your `FilterChainProxy`.
-The `ConcurrentSessionFilter` requires two constructor arguments:
-* `sessionRegistry`, which generally points to an instance of `SessionRegistryImpl`
-* `sessionInformationExpiredStrategy`, which defines the strategy to apply when a session has expired
-The following sample configuration uses the namespace to create the `FilterChainProxy` and other default beans:
-
-====
-[source,xml]
-----
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
----
====
-Adding the listener to `web.xml` causes an `ApplicationEvent` to be published to the Spring `ApplicationContext` every time a `HttpSession` commences or ends.
-This is critical, as it lets the `SessionRegistryImpl` be notified when a session ends.
-Without it, a user can never log back in again once they have exceeded their session allowance, even if they log out of another session or it times out.
+== What to read next
-[[list-authenticated-principals]]
-=== Querying the SessionRegistry for currently authenticated users and their sessions
-Setting up concurrency control, either through the namespace or using plain beans has the useful side effect of providing you with a reference to the `SessionRegistry` that you can use directly within your application. So, even if you do not want to restrict the number of sessions a user may have, it may be worth setting up the infrastructure anyway.
-You can set the `maximumSession` property to `-1` to allow unlimited sessions.
-If you use the namespace, you can set an alias for the internally-created `SessionRegistry` by using the `session-registry-alias` attribute, providing a reference that you can inject into your own beans.
-
-The `getAllPrincipals()` method supplies you with a list of the currently authenticated users.
-You can list a user's sessions by calling the `getAllSessions(Object principal, boolean includeExpiredSessions)` method, which returns a list of `SessionInformation` objects.
-You can also expire a user's session by calling `expireNow()` on a `SessionInformation` instance.
-When the user returns to the application, they are prevented from proceeding.
-You may find these methods useful in an administration application, for example.
-See the Javadoc for more information about the {security-api-url}org/springframework/security/core/session/SessionRegistry.html[`SessionRegistry`] interface.
+- Clustered sessions with https://docs.spring.io/spring-session/reference/index.html[Spring Session]