123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465 |
- [[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 {security-api-url}org/springframework/security/web/server/authentication/ConcurrentSessionControlServerAuthenticationSuccessHandler.html[ConcurrentSessionControlServerAuthenticationSuccessHandler] and the {security-api-url}org/springframework/security/web/server/authentication/RegisterSessionServerAuthenticationSuccessHandler.html[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(WebSessionManager webSessionManager) {
- return new WebSessionStoreReactiveSessionRegistry(((DefaultWebSessionManager) webSessionManager).getSessionStore());
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Bean
- open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
- return http {
- // ...
- sessionManagement {
- sessionConcurrency {
- maximumSessions = SessionLimit.of(1)
- }
- }
- }
- }
- @Bean
- open fun reactiveSessionRegistry(webSessionManager: WebSessionManager): ReactiveSessionRegistry {
- return WebSessionStoreReactiveSessionRegistry((webSessionManager as DefaultWebSessionManager).sessionStore)
- }
- ----
- ======
- 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(WebSessionManager webSessionManager) {
- return new WebSessionStoreReactiveSessionRegistry(((DefaultWebSessionManager) webSessionManager).getSessionStore());
- }
- ----
- 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 WebSessionStoreReactiveSessionRegistry((webSessionManager as DefaultWebSessionManager).sessionStore)
- }
- ----
- ======
- 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(WebSessionManager webSessionManager) {
- return new WebSessionStoreReactiveSessionRegistry(((DefaultWebSessionManager) webSessionManager).getSessionStore());
- }
- ----
- 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(webSessionManager: WebSessionManager): ReactiveSessionRegistry {
- return WebSessionStoreReactiveSessionRegistry((webSessionManager as DefaultWebSessionManager).sessionStore)
- }
- ----
- ======
- 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>>.
- [[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 {security-api-url}org/springframework/security/web/server/authentication/session/InvalidateLeastUsedMaximumSessionsExceededHandler.html[InvalidateLeastUsedMaximumSessionsExceededHandler].
- Spring Security also provides another implementation that prevents the user from creating new sessions by using the {security-api-url}org/springframework/security/web/server/authentication/session/PreventLoginMaximumSessionsExceededHandler.html[PreventLoginMaximumSessionsExceededHandler].
- If you want to use your own strategy, you can provide a different implementation of {security-api-url}org/springframework/security/web/server/authentication/session/ServerMaximumSessionsExceededHandler.html[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(WebSessionManager webSessionManager) {
- return new WebSessionStoreReactiveSessionRegistry(((DefaultWebSessionManager) webSessionManager).getSessionStore());
- }
- ----
- 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(webSessionManager: WebSessionManager): ReactiveSessionRegistry {
- return WebSessionStoreReactiveSessionRegistry((webSessionManager as DefaultWebSessionManager).sessionStore)
- }
- ----
- ======
- [[reactive-concurrent-sessions-control-specify-session-registry]]
- == Specifying a `ReactiveSessionRegistry`
- In order to keep track of the user's sessions, Spring Security uses a {security-api-url}org/springframework/security/core/session/ReactiveSessionRegistry.html[ReactiveSessionRegistry], and, every time a user logs in, their session information is saved.
- Typically, in a Spring WebFlux application, you will use the {security-api-url}/org/springframework/security/web/session/WebSessionStoreReactiveSessionRegistry.html[WebSessionStoreReactiveSessionRegistry] which makes sure that the `WebSession` is invalidated whenever the `ReactiveSessionInformation` is invalidated.
- Spring Security ships with {security-api-url}/org/springframework/security/web/session/WebSessionStoreReactiveSessionRegistry.html[WebSessionStoreReactiveSessionRegistry] and {security-api-url}org/springframework/security/core/session/InMemoryReactiveSessionRegistry.html[InMemoryReactiveSessionRegistry] implementations of `ReactiveSessionRegistry`.
- [NOTE]
- ====
- When creating the `WebSessionStoreReactiveSessionRegistry`, you need to provide the `WebSessionStore` that is being used by your application.
- If you are using Spring WebFlux, you can use the `WebSessionManager` bean (which is usually an instance of `DefaultWebSessionManager`) to get the `WebSessionStore`.
- ====
- 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 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()
- }
- ----
- ======
- 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 InMemoryReactiveSessionRegistry())
- )
- );
- return http.build();
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Bean
- open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
- return http {
- // ...
- sessionManagement {
- sessionConcurrency {
- maximumSessions = SessionLimit.of(1)
- sessionRegistry = InMemoryReactiveSessionRegistry()
- }
- }
- }
- }
- ----
- ======
- [[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 and then invalidate them:
- .Using ReactiveSessionRegistry to invalidate sessions manually
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- public class SessionControl {
- private final ReactiveSessionRegistry reactiveSessionRegistry;
- public SessionControl(ReactiveSessionRegistry reactiveSessionRegistry) {
- this.reactiveSessionRegistry = reactiveSessionRegistry;
- }
- public Mono<Void> invalidateSessions(String username) {
- return this.reactiveSessionRegistry.getAllSessions(username)
- .flatMap(ReactiveSessionInformation::invalidate)
- .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].
|