mirror of
				https://github.com/spring-projects/spring-security.git
				synced 2025-10-25 19:58:48 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			465 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			465 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| [[reactive-concurrent-sessions-control]]
 | |
| = Concurrent Sessions Control
 | |
| 
 | |
| Similar to xref:servlet/authentication/session-management.adoc#ns-concurrent-sessions[Servlet's Concurrent Sessions Control], Spring Security also provides support to limit the number of concurrent sessions a user can have in a Reactive application.
 | |
| 
 | |
| When you set up Concurrent Sessions Control in Spring Security, it monitors authentications carried out through Form Login, xref:reactive/oauth2/login/index.adoc[OAuth 2.0 Login], and HTTP Basic authentication by hooking into the way those authentication mechanisms handle authentication success.
 | |
| More specifically, the session management DSL will add the javadoc:org.springframework.security.web.server.authentication.ConcurrentSessionControlServerAuthenticationSuccessHandler[] and the javadoc:org.springframework.security.web.server.authentication.RegisterSessionServerAuthenticationSuccessHandler[] to the list of `ServerAuthenticationSuccessHandler` used by the authentication filter.
 | |
| 
 | |
| The following sections contains examples of how to configure Concurrent Sessions Control.
 | |
| 
 | |
| * <<reactive-concurrent-sessions-control-limit,I want to limit the number of concurrent sessions a user can have>>
 | |
| * <<concurrent-sessions-control-custom-strategy,I want to customize the strategy used when the maximum number of sessions is exceeded>>
 | |
| * <<reactive-concurrent-sessions-control-specify-session-registry,I want to know how to specify a `ReactiveSessionRegistry`>>
 | |
| * <<concurrent-sessions-control-sample,I want to see a sample application that uses Concurrent Sessions Control>>
 | |
| * <<disabling-for-authentication-filters,I want to know how to disable it for some authentication filter>>
 | |
| 
 | |
| [[reactive-concurrent-sessions-control-limit]]
 | |
| == Limiting Concurrent Sessions
 | |
| 
 | |
| By default, Spring Security will allow any number of concurrent sessions for a user.
 | |
| To limit the number of concurrent sessions, you can use the `maximumSessions` DSL method:
 | |
| 
 | |
| .Configuring one session for any user
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @Bean
 | |
| SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
 | |
|     http
 | |
|         // ...
 | |
|         .sessionManagement((sessions) -> sessions
 | |
|             .concurrentSessions((concurrency) -> concurrency
 | |
|                 .maximumSessions(SessionLimit.of(1))
 | |
|             )
 | |
|         );
 | |
|     return http.build();
 | |
| }
 | |
| 
 | |
| @Bean
 | |
| ReactiveSessionRegistry reactiveSessionRegistry() {
 | |
|     return new InMemoryReactiveSessionRegistry();
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Bean
 | |
| open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
 | |
|     return http {
 | |
|         // ...
 | |
|         sessionManagement {
 | |
|             sessionConcurrency {
 | |
|                 maximumSessions = SessionLimit.of(1)
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| @Bean
 | |
| open fun reactiveSessionRegistry(): ReactiveSessionRegistry {
 | |
|     return InMemoryReactiveSessionRegistry()
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| The above configuration allows one session for any user.
 | |
| Similarly, you can also allow unlimited sessions by using the `SessionLimit#UNLIMITED` constant:
 | |
| 
 | |
| .Configuring unlimited sessions
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @Bean
 | |
| SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
 | |
|     http
 | |
|         // ...
 | |
|         .sessionManagement((sessions) -> sessions
 | |
|             .concurrentSessions((concurrency) -> concurrency
 | |
|                 .maximumSessions(SessionLimit.UNLIMITED))
 | |
|         );
 | |
|     return http.build();
 | |
| }
 | |
| 
 | |
| @Bean
 | |
| ReactiveSessionRegistry reactiveSessionRegistry() {
 | |
|     return new InMemoryReactiveSessionRegistry();
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Bean
 | |
| open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
 | |
|     return http {
 | |
|         // ...
 | |
|         sessionManagement {
 | |
|             sessionConcurrency {
 | |
|                 maximumSessions = SessionLimit.UNLIMITED
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| @Bean
 | |
| open fun reactiveSessionRegistry(webSessionManager: WebSessionManager): ReactiveSessionRegistry {
 | |
|     return InMemoryReactiveSessionRegistry()
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| Since the `maximumSessions` method accepts a `SessionLimit` interface, which in turn extends `Function<Authentication, Mono<Integer>>`, you can have a more complex logic to determine the maximum number of sessions based on the user's authentication:
 | |
| 
 | |
| .Configuring maximumSessions based on `Authentication`
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @Bean
 | |
| SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
 | |
|     http
 | |
|         // ...
 | |
|         .sessionManagement((sessions) -> sessions
 | |
|             .concurrentSessions((concurrency) -> concurrency
 | |
|                 .maximumSessions(maxSessions()))
 | |
|         );
 | |
|     return http.build();
 | |
| }
 | |
| 
 | |
| private SessionLimit maxSessions() {
 | |
|     return (authentication) -> {
 | |
|         if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_UNLIMITED_SESSIONS"))) {
 | |
|             return Mono.empty(); // allow unlimited sessions for users with ROLE_UNLIMITED_SESSIONS
 | |
|         }
 | |
|         if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_ADMIN"))) {
 | |
|             return Mono.just(2); // allow two sessions for admins
 | |
|         }
 | |
|         return Mono.just(1); // allow one session for every other user
 | |
|     };
 | |
| }
 | |
| 
 | |
| @Bean
 | |
| ReactiveSessionRegistry reactiveSessionRegistry() {
 | |
|     return new InMemoryReactiveSessionRegistry();
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Bean
 | |
| open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
 | |
|     return http {
 | |
|         // ...
 | |
|         sessionManagement {
 | |
|             sessionConcurrency {
 | |
|                 maximumSessions = maxSessions()
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| fun maxSessions(): SessionLimit {
 | |
|     return { authentication ->
 | |
|         if (authentication.authorities.contains(SimpleGrantedAuthority("ROLE_UNLIMITED_SESSIONS"))) Mono.empty
 | |
|         if (authentication.authorities.contains(SimpleGrantedAuthority("ROLE_ADMIN"))) Mono.just(2)
 | |
|         Mono.just(1)
 | |
|     }
 | |
| }
 | |
| 
 | |
| @Bean
 | |
| open fun reactiveSessionRegistry(): ReactiveSessionRegistry {
 | |
|     return InMemoryReactiveSessionRegistry()
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| When the maximum number of sessions is exceeded, by default, the least recently used session(s) will be expired.
 | |
| If you want to change that behavior, you can <<concurrent-sessions-control-custom-strategy,customize the strategy used when the maximum number of sessions is exceeded>>.
 | |
| 
 | |
| [IMPORTANT]
 | |
| ====
 | |
| The Concurrent Session Management is not aware if there is another session in some Identity Provider that you might use via xref:reactive/oauth2/login/index.adoc[OAuth 2 Login] for example.
 | |
| If you also need to invalidate the session against the Identity Provider you must <<concurrent-sessions-control-custom-strategy,include your own implementation of `ServerMaximumSessionsExceededHandler`>>.
 | |
| ====
 | |
| 
 | |
| [[concurrent-sessions-control-custom-strategy]]
 | |
| == Handling Maximum Number of Sessions Exceeded
 | |
| 
 | |
| By default, when the maximum number of sessions is exceeded, the least recently used session(s) will be expired by using the javadoc:org.springframework.security.web.server.authentication.InvalidateLeastUsedServerMaximumSessionsExceededHandler[].
 | |
| Spring Security also provides another implementation that prevents the user from creating new sessions by using the javadoc:org.springframework.security.web.server.authentication.PreventLoginServerMaximumSessionsExceededHandler[].
 | |
| If you want to use your own strategy, you can provide a different implementation of javadoc:org.springframework.security.web.server.authentication.ServerMaximumSessionsExceededHandler[].
 | |
| 
 | |
| .Configuring maximumSessionsExceededHandler
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @Bean
 | |
| SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
 | |
|     http
 | |
|         // ...
 | |
|         .sessionManagement((sessions) -> sessions
 | |
|             .concurrentSessions((concurrency) -> concurrency
 | |
|                 .maximumSessions(SessionLimit.of(1))
 | |
|                 .maximumSessionsExceededHandler(new PreventLoginMaximumSessionsExceededHandler())
 | |
|             )
 | |
|         );
 | |
|     return http.build();
 | |
| }
 | |
| 
 | |
| @Bean
 | |
| ReactiveSessionRegistry reactiveSessionRegistry() {
 | |
|     return new InMemoryReactiveSessionRegistry();
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Bean
 | |
| open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
 | |
|     return http {
 | |
|         // ...
 | |
|         sessionManagement {
 | |
|             sessionConcurrency {
 | |
|                 maximumSessions = SessionLimit.of(1)
 | |
|                 maximumSessionsExceededHandler = PreventLoginMaximumSessionsExceededHandler()
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| @Bean
 | |
| open fun reactiveSessionRegistry(): ReactiveSessionRegistry {
 | |
|     return InMemoryReactiveSessionRegistry()
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| [[reactive-concurrent-sessions-control-specify-session-registry]]
 | |
| == Specifying a `ReactiveSessionRegistry`
 | |
| 
 | |
| In order to keep track of the user's sessions, Spring Security uses a javadoc:org.springframework.security.core.session.ReactiveSessionRegistry[], and, every time a user logs in, their session information is saved.
 | |
| 
 | |
| Spring Security ships with javadoc:org.springframework.security.core.session.InMemoryReactiveSessionRegistry[] implementation of `ReactiveSessionRegistry`.
 | |
| 
 | |
| To specify a `ReactiveSessionRegistry` implementation you can either declare it as a bean:
 | |
| 
 | |
| .ReactiveSessionRegistry as a Bean
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @Bean
 | |
| SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
 | |
|     http
 | |
|         // ...
 | |
|         .sessionManagement((sessions) -> sessions
 | |
|             .concurrentSessions((concurrency) -> concurrency
 | |
|                 .maximumSessions(SessionLimit.of(1))
 | |
|             )
 | |
|         );
 | |
|     return http.build();
 | |
| }
 | |
| 
 | |
| @Bean
 | |
| ReactiveSessionRegistry reactiveSessionRegistry() {
 | |
|     return new MyReactiveSessionRegistry();
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Bean
 | |
| open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
 | |
|     return http {
 | |
|         // ...
 | |
|         sessionManagement {
 | |
|             sessionConcurrency {
 | |
|                 maximumSessions = SessionLimit.of(1)
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| @Bean
 | |
| open fun reactiveSessionRegistry(): ReactiveSessionRegistry {
 | |
|     return MyReactiveSessionRegistry()
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| or you can use the `sessionRegistry` DSL method:
 | |
| 
 | |
| .ReactiveSessionRegistry using sessionRegistry DSL method
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @Bean
 | |
| SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
 | |
|     http
 | |
|         // ...
 | |
|         .sessionManagement((sessions) -> sessions
 | |
|             .concurrentSessions((concurrency) -> concurrency
 | |
|                 .maximumSessions(SessionLimit.of(1))
 | |
|                 .sessionRegistry(new MyReactiveSessionRegistry())
 | |
|             )
 | |
|         );
 | |
|     return http.build();
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Bean
 | |
| open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
 | |
|     return http {
 | |
|         // ...
 | |
|         sessionManagement {
 | |
|             sessionConcurrency {
 | |
|                 maximumSessions = SessionLimit.of(1)
 | |
|                 sessionRegistry = MyReactiveSessionRegistry()
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| [[reactive-concurrent-sessions-control-manually-invalidating-sessions]]
 | |
| == Invalidating Registered User's Sessions
 | |
| 
 | |
| At times, it is handy to be able to invalidate all or some of a user's sessions.
 | |
| For example, when a user changes their password, you may want to invalidate all of their sessions so that they are forced to log in again.
 | |
| To do that, you can use the `ReactiveSessionRegistry` bean to retrieve all the user's sessions, invalidate them, and them remove them from the `WebSessionStore`:
 | |
| 
 | |
| .Using ReactiveSessionRegistry to invalidate sessions manually
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| public class SessionControl {
 | |
|     private final ReactiveSessionRegistry reactiveSessionRegistry;
 | |
| 
 | |
|     private final WebSessionStore webSessionStore;
 | |
| 
 | |
|     public Mono<Void> invalidateSessions(String username) {
 | |
|         return this.reactiveSessionRegistry.getAllSessions(username)
 | |
|             .flatMap((session) -> session.invalidate().thenReturn(session))
 | |
|             .flatMap((session) -> this.webSessionStore.removeSession(session.getSessionId()))
 | |
|             .then();
 | |
|     }
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| [[disabling-for-authentication-filters]]
 | |
| == Disabling It for Some Authentication Filters
 | |
| 
 | |
| By default, Concurrent Sessions Control will be configured automatically for Form Login, OAuth 2.0 Login, and HTTP Basic authentication as long as they do not specify an `ServerAuthenticationSuccessHandler` themselves.
 | |
| For example, the following configuration will disable Concurrent Sessions Control for Form Login:
 | |
| 
 | |
| .Disabling Concurrent Sessions Control for Form Login
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @Bean
 | |
| SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
 | |
|     http
 | |
|         // ...
 | |
|         .formLogin((login) -> login
 | |
|             .authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler("/"))
 | |
|         )
 | |
|         .sessionManagement((sessions) -> sessions
 | |
|             .concurrentSessions((concurrency) -> concurrency
 | |
|                 .maximumSessions(SessionLimit.of(1))
 | |
|             )
 | |
|         );
 | |
|     return http.build();
 | |
| }
 | |
| ----
 | |
| 
 | |
| Kotlin::
 | |
| +
 | |
| [source,kotlin,role="secondary"]
 | |
| ----
 | |
| @Bean
 | |
| open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
 | |
|     return http {
 | |
|         // ...
 | |
|         formLogin {
 | |
|             authenticationSuccessHandler = RedirectServerAuthenticationSuccessHandler("/")
 | |
|         }
 | |
|         sessionManagement {
 | |
|             sessionConcurrency {
 | |
|                 maximumSessions = SessionLimit.of(1)
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| === Adding Additional Success Handlers Without Disabling Concurrent Sessions Control
 | |
| 
 | |
| You can also include additional `ServerAuthenticationSuccessHandler` instances to the list of handlers used by the authentication filter without disabling Concurrent Sessions Control.
 | |
| To do that you can use the `authenticationSuccessHandler(Consumer<List<ServerAuthenticationSuccessHandler>>)` method:
 | |
| 
 | |
| .Adding additional handlers
 | |
| [tabs]
 | |
| ======
 | |
| Java::
 | |
| +
 | |
| [source,java,role="primary"]
 | |
| ----
 | |
| @Bean
 | |
| SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
 | |
|     http
 | |
|         // ...
 | |
|         .formLogin((login) -> login
 | |
|             .authenticationSuccessHandler((handlers) -> handlers.add(new MyAuthenticationSuccessHandler()))
 | |
|         )
 | |
|         .sessionManagement((sessions) -> sessions
 | |
|             .concurrentSessions((concurrency) -> concurrency
 | |
|                 .maximumSessions(SessionLimit.of(1))
 | |
|             )
 | |
|         );
 | |
|     return http.build();
 | |
| }
 | |
| ----
 | |
| ======
 | |
| 
 | |
| [[concurrent-sessions-control-sample]]
 | |
| == Checking a Sample Application
 | |
| 
 | |
| You can check the {gh-samples-url}/reactive/webflux/java/session-management/maximum-sessions[sample application here].
 |