浏览代码

Update Max Sessions on WebFlux

Delete WebSessionStoreReactiveSessionRegistry.java and gives the responsibility to remove the sessions from the WebSessionStore to the handler

Issue gh-6192
Marcus Hert Da Coregio 1 年之前
父节点
当前提交
f8ff056eb6

+ 22 - 3
config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java

@@ -214,6 +214,8 @@ import org.springframework.web.server.ServerWebExchangeDecorator;
 import org.springframework.web.server.WebFilter;
 import org.springframework.web.server.WebFilter;
 import org.springframework.web.server.WebFilterChain;
 import org.springframework.web.server.WebFilterChain;
 import org.springframework.web.server.WebSession;
 import org.springframework.web.server.WebSession;
+import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
+import org.springframework.web.server.session.DefaultWebSessionManager;
 import org.springframework.web.util.pattern.PathPatternParser;
 import org.springframework.web.util.pattern.PathPatternParser;
 
 
 /**
 /**
@@ -1964,7 +1966,7 @@ public class ServerHttpSecurity {
 
 
 		private SessionLimit sessionLimit = SessionLimit.UNLIMITED;
 		private SessionLimit sessionLimit = SessionLimit.UNLIMITED;
 
 
-		private ServerMaximumSessionsExceededHandler maximumSessionsExceededHandler = new InvalidateLeastUsedServerMaximumSessionsExceededHandler();
+		private ServerMaximumSessionsExceededHandler maximumSessionsExceededHandler;
 
 
 		/**
 		/**
 		 * Configures how many sessions are allowed for a given user.
 		 * Configures how many sessions are allowed for a given user.
@@ -1983,9 +1985,8 @@ public class ServerHttpSecurity {
 			if (this.concurrentSessions != null) {
 			if (this.concurrentSessions != null) {
 				ReactiveSessionRegistry reactiveSessionRegistry = getSessionRegistry();
 				ReactiveSessionRegistry reactiveSessionRegistry = getSessionRegistry();
 				ConcurrentSessionControlServerAuthenticationSuccessHandler concurrentSessionControlStrategy = new ConcurrentSessionControlServerAuthenticationSuccessHandler(
 				ConcurrentSessionControlServerAuthenticationSuccessHandler concurrentSessionControlStrategy = new ConcurrentSessionControlServerAuthenticationSuccessHandler(
-						reactiveSessionRegistry);
+						reactiveSessionRegistry, getMaximumSessionsExceededHandler());
 				concurrentSessionControlStrategy.setSessionLimit(this.sessionLimit);
 				concurrentSessionControlStrategy.setSessionLimit(this.sessionLimit);
-				concurrentSessionControlStrategy.setMaximumSessionsExceededHandler(this.maximumSessionsExceededHandler);
 				RegisterSessionServerAuthenticationSuccessHandler registerSessionAuthenticationStrategy = new RegisterSessionServerAuthenticationSuccessHandler(
 				RegisterSessionServerAuthenticationSuccessHandler registerSessionAuthenticationStrategy = new RegisterSessionServerAuthenticationSuccessHandler(
 						reactiveSessionRegistry);
 						reactiveSessionRegistry);
 				this.authenticationSuccessHandler = new DelegatingServerAuthenticationSuccessHandler(
 				this.authenticationSuccessHandler = new DelegatingServerAuthenticationSuccessHandler(
@@ -1997,6 +1998,24 @@ public class ServerHttpSecurity {
 			}
 			}
 		}
 		}
 
 
+		private ServerMaximumSessionsExceededHandler getMaximumSessionsExceededHandler() {
+			if (this.maximumSessionsExceededHandler != null) {
+				return this.maximumSessionsExceededHandler;
+			}
+			DefaultWebSessionManager webSessionManager = getBeanOrNull(
+					WebHttpHandlerBuilder.WEB_SESSION_MANAGER_BEAN_NAME, DefaultWebSessionManager.class);
+			if (webSessionManager != null) {
+				this.maximumSessionsExceededHandler = new InvalidateLeastUsedServerMaximumSessionsExceededHandler(
+						webSessionManager.getSessionStore());
+			}
+			if (this.maximumSessionsExceededHandler == null) {
+				throw new IllegalStateException(
+						"Could not create a default ServerMaximumSessionsExceededHandler. Please provide "
+								+ "a ServerMaximumSessionsExceededHandler via DSL");
+			}
+			return this.maximumSessionsExceededHandler;
+		}
+
 		private void configureSuccessHandlerOnAuthenticationFilters() {
 		private void configureSuccessHandlerOnAuthenticationFilters() {
 			if (ServerHttpSecurity.this.formLogin != null) {
 			if (ServerHttpSecurity.this.formLogin != null) {
 				ServerHttpSecurity.this.formLogin.defaultSuccessHandlers.add(0, this.authenticationSuccessHandler);
 				ServerHttpSecurity.this.formLogin.defaultSuccessHandlers.add(0, this.authenticationSuccessHandler);

+ 6 - 49
config/src/test/java/org/springframework/security/config/web/server/SessionManagementSpecTests.java

@@ -34,9 +34,8 @@ import org.springframework.security.config.annotation.web.reactive.EnableWebFlux
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContextExtension;
 import org.springframework.security.config.test.SpringTestContextExtension;
 import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration;
 import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration;
-import org.springframework.security.core.session.ReactiveSessionInformation;
+import org.springframework.security.core.session.InMemoryReactiveSessionRegistry;
 import org.springframework.security.core.session.ReactiveSessionRegistry;
 import org.springframework.security.core.session.ReactiveSessionRegistry;
-import org.springframework.security.core.userdetails.PasswordEncodedUser;
 import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
 import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
 import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
 import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
 import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
 import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
@@ -52,10 +51,8 @@ import org.springframework.security.web.server.authentication.InvalidateLeastUse
 import org.springframework.security.web.server.authentication.PreventLoginServerMaximumSessionsExceededHandler;
 import org.springframework.security.web.server.authentication.PreventLoginServerMaximumSessionsExceededHandler;
 import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler;
 import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler;
 import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
 import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
-import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
 import org.springframework.security.web.server.authentication.SessionLimit;
 import org.springframework.security.web.server.authentication.SessionLimit;
 import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
 import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
-import org.springframework.security.web.session.WebSessionStoreReactiveSessionRegistry;
 import org.springframework.test.context.junit.jupiter.SpringExtension;
 import org.springframework.test.context.junit.jupiter.SpringExtension;
 import org.springframework.test.web.reactive.server.WebTestClient;
 import org.springframework.test.web.reactive.server.WebTestClient;
 import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.LinkedMultiValueMap;
@@ -322,45 +319,6 @@ public class SessionManagementSpecTests {
 		// @formatter:on
 		// @formatter:on
 	}
 	}
 
 
-	@Test
-	void loginWhenUnlimitedSessionsButSessionsInvalidatedManuallyThenInvalidates() {
-		ConcurrentSessionsMaxSessionPreventsLoginFalseConfig.sessionLimit = SessionLimit.UNLIMITED;
-		this.spring.register(ConcurrentSessionsMaxSessionPreventsLoginFalseConfig.class).autowire();
-		MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
-		data.add("username", "user");
-		data.add("password", "password");
-
-		ResponseCookie firstLogin = loginReturningCookie(data);
-		ResponseCookie secondLogin = loginReturningCookie(data);
-		this.client.get().uri("/").cookie(firstLogin.getName(), firstLogin.getValue()).exchange().expectStatus().isOk();
-		this.client.get()
-			.uri("/")
-			.cookie(secondLogin.getName(), secondLogin.getValue())
-			.exchange()
-			.expectStatus()
-			.isOk();
-		ReactiveSessionRegistry sessionRegistry = this.spring.getContext().getBean(ReactiveSessionRegistry.class);
-		sessionRegistry.getAllSessions(PasswordEncodedUser.user())
-			.flatMap(ReactiveSessionInformation::invalidate)
-			.blockLast();
-		this.client.get()
-			.uri("/")
-			.cookie(firstLogin.getName(), firstLogin.getValue())
-			.exchange()
-			.expectStatus()
-			.isFound()
-			.expectHeader()
-			.location("/login");
-		this.client.get()
-			.uri("/")
-			.cookie(secondLogin.getName(), secondLogin.getValue())
-			.exchange()
-			.expectStatus()
-			.isFound()
-			.expectHeader()
-			.location("/login");
-	}
-
 	@Test
 	@Test
 	void oauth2LoginWhenMaxSessionDoesNotPreventLoginThenSecondLoginSucceedsAndFirstSessionIsInvalidated() {
 	void oauth2LoginWhenMaxSessionDoesNotPreventLoginThenSecondLoginSucceedsAndFirstSessionIsInvalidated() {
 		OAuth2LoginConcurrentSessionsConfig.maxSessions = 1;
 		OAuth2LoginConcurrentSessionsConfig.maxSessions = 1;
@@ -490,10 +448,9 @@ public class SessionManagementSpecTests {
 
 
 		ServerOAuth2AuthorizationRequestResolver resolver = mock(ServerOAuth2AuthorizationRequestResolver.class);
 		ServerOAuth2AuthorizationRequestResolver resolver = mock(ServerOAuth2AuthorizationRequestResolver.class);
 
 
-		ServerAuthenticationSuccessHandler successHandler = mock(ServerAuthenticationSuccessHandler.class);
-
 		@Bean
 		@Bean
-		SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) {
+		SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http,
+				DefaultWebSessionManager webSessionManager) {
 			// @formatter:off
 			// @formatter:off
 			http
 			http
 					.authorizeExchange((exchanges) -> exchanges
 					.authorizeExchange((exchanges) -> exchanges
@@ -509,7 +466,7 @@ public class SessionManagementSpecTests {
 								.maximumSessions(SessionLimit.of(maxSessions))
 								.maximumSessions(SessionLimit.of(maxSessions))
 								.maximumSessionsExceededHandler(preventLogin
 								.maximumSessionsExceededHandler(preventLogin
 										? new PreventLoginServerMaximumSessionsExceededHandler()
 										? new PreventLoginServerMaximumSessionsExceededHandler()
-										: new InvalidateLeastUsedServerMaximumSessionsExceededHandler())
+										: new InvalidateLeastUsedServerMaximumSessionsExceededHandler(webSessionManager.getSessionStore()))
 						)
 						)
 					);
 					);
 			// @formatter:on
 			// @formatter:on
@@ -611,8 +568,8 @@ public class SessionManagementSpecTests {
 		}
 		}
 
 
 		@Bean
 		@Bean
-		ReactiveSessionRegistry reactiveSessionRegistry(DefaultWebSessionManager webSessionManager) {
-			return new WebSessionStoreReactiveSessionRegistry(webSessionManager.getSessionStore());
+		ReactiveSessionRegistry reactiveSessionRegistry() {
+			return new InMemoryReactiveSessionRegistry();
 		}
 		}
 
 
 	}
 	}

+ 6 - 7
config/src/test/kotlin/org/springframework/security/config/web/server/ServerSessionManagementDslTests.kt

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright 2002-2023 the original author or authors.
+ * Copyright 2002-2024 the original author or authors.
  *
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * you may not use this file except in compliance with the License.
@@ -29,13 +29,13 @@ import org.springframework.security.config.annotation.web.reactive.EnableWebFlux
 import org.springframework.security.config.test.SpringTestContext
 import org.springframework.security.config.test.SpringTestContext
 import org.springframework.security.config.test.SpringTestContextExtension
 import org.springframework.security.config.test.SpringTestContextExtension
 import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration
 import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration
+import org.springframework.security.core.session.InMemoryReactiveSessionRegistry
 import org.springframework.security.core.session.ReactiveSessionRegistry
 import org.springframework.security.core.session.ReactiveSessionRegistry
 import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers
 import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers
 import org.springframework.security.web.server.SecurityWebFilterChain
 import org.springframework.security.web.server.SecurityWebFilterChain
 import org.springframework.security.web.server.authentication.InvalidateLeastUsedServerMaximumSessionsExceededHandler
 import org.springframework.security.web.server.authentication.InvalidateLeastUsedServerMaximumSessionsExceededHandler
 import org.springframework.security.web.server.authentication.PreventLoginServerMaximumSessionsExceededHandler
 import org.springframework.security.web.server.authentication.PreventLoginServerMaximumSessionsExceededHandler
 import org.springframework.security.web.server.authentication.SessionLimit
 import org.springframework.security.web.server.authentication.SessionLimit
-import org.springframework.security.web.session.WebSessionStoreReactiveSessionRegistry
 import org.springframework.test.web.reactive.server.WebTestClient
 import org.springframework.test.web.reactive.server.WebTestClient
 import org.springframework.util.LinkedMultiValueMap
 import org.springframework.util.LinkedMultiValueMap
 import org.springframework.util.MultiValueMap
 import org.springframework.util.MultiValueMap
@@ -45,7 +45,6 @@ import org.springframework.web.reactive.config.EnableWebFlux
 import org.springframework.web.reactive.function.BodyInserters
 import org.springframework.web.reactive.function.BodyInserters
 import org.springframework.web.server.adapter.WebHttpHandlerBuilder
 import org.springframework.web.server.adapter.WebHttpHandlerBuilder
 import org.springframework.web.server.session.DefaultWebSessionManager
 import org.springframework.web.server.session.DefaultWebSessionManager
-import reactor.core.publisher.Mono
 
 
 /**
 /**
  * Tests for [ServerSessionManagementDsl]
  * Tests for [ServerSessionManagementDsl]
@@ -208,7 +207,7 @@ class ServerSessionManagementDslTests {
         }
         }
 
 
         @Bean
         @Bean
-        open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
+        open fun springSecurity(http: ServerHttpSecurity, webSessionManager: DefaultWebSessionManager): SecurityWebFilterChain {
             return http {
             return http {
                 authorizeExchange {
                 authorizeExchange {
                     authorize(anyExchange, authenticated)
                     authorize(anyExchange, authenticated)
@@ -217,7 +216,7 @@ class ServerSessionManagementDslTests {
                 sessionManagement {
                 sessionManagement {
                     sessionConcurrency {
                     sessionConcurrency {
                         maximumSessions = SessionLimit.of(maxSessions)
                         maximumSessions = SessionLimit.of(maxSessions)
-                        maximumSessionsExceededHandler = InvalidateLeastUsedServerMaximumSessionsExceededHandler()
+                        maximumSessionsExceededHandler = InvalidateLeastUsedServerMaximumSessionsExceededHandler(webSessionManager.sessionStore)
                     }
                     }
                 }
                 }
             }
             }
@@ -263,8 +262,8 @@ class ServerSessionManagementDslTests {
         }
         }
 
 
         @Bean
         @Bean
-        open fun reactiveSessionRegistry(webSessionManager: DefaultWebSessionManager): ReactiveSessionRegistry {
-            return WebSessionStoreReactiveSessionRegistry(webSessionManager.sessionStore)
+        open fun reactiveSessionRegistry(): ReactiveSessionRegistry {
+            return InMemoryReactiveSessionRegistry()
         }
         }
 
 
     }
     }

+ 25 - 32
docs/modules/ROOT/pages/reactive/authentication/concurrent-sessions-control.adoc

@@ -34,13 +34,14 @@ SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
         .sessionManagement((sessions) -> sessions
         .sessionManagement((sessions) -> sessions
             .concurrentSessions((concurrency) -> concurrency
             .concurrentSessions((concurrency) -> concurrency
                 .maximumSessions(SessionLimit.of(1))
                 .maximumSessions(SessionLimit.of(1))
+            )
         );
         );
     return http.build();
     return http.build();
 }
 }
 
 
 @Bean
 @Bean
-ReactiveSessionRegistry reactiveSessionRegistry(WebSessionManager webSessionManager) {
-    return new WebSessionStoreReactiveSessionRegistry(((DefaultWebSessionManager) webSessionManager).getSessionStore());
+ReactiveSessionRegistry reactiveSessionRegistry() {
+    return new InMemoryReactiveSessionRegistry();
 }
 }
 ----
 ----
 
 
@@ -60,8 +61,8 @@ open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
     }
     }
 }
 }
 @Bean
 @Bean
-open fun reactiveSessionRegistry(webSessionManager: WebSessionManager): ReactiveSessionRegistry {
-    return WebSessionStoreReactiveSessionRegistry((webSessionManager as DefaultWebSessionManager).sessionStore)
+open fun reactiveSessionRegistry(): ReactiveSessionRegistry {
+    return InMemoryReactiveSessionRegistry()
 }
 }
 ----
 ----
 ======
 ======
@@ -88,8 +89,8 @@ SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
 }
 }
 
 
 @Bean
 @Bean
-ReactiveSessionRegistry reactiveSessionRegistry(WebSessionManager webSessionManager) {
-    return new WebSessionStoreReactiveSessionRegistry(((DefaultWebSessionManager) webSessionManager).getSessionStore());
+ReactiveSessionRegistry reactiveSessionRegistry() {
+    return new InMemoryReactiveSessionRegistry();
 }
 }
 ----
 ----
 
 
@@ -110,7 +111,7 @@ open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
 }
 }
 @Bean
 @Bean
 open fun reactiveSessionRegistry(webSessionManager: WebSessionManager): ReactiveSessionRegistry {
 open fun reactiveSessionRegistry(webSessionManager: WebSessionManager): ReactiveSessionRegistry {
-    return WebSessionStoreReactiveSessionRegistry((webSessionManager as DefaultWebSessionManager).sessionStore)
+    return InMemoryReactiveSessionRegistry()
 }
 }
 ----
 ----
 ======
 ======
@@ -148,8 +149,8 @@ private SessionLimit maxSessions() {
 }
 }
 
 
 @Bean
 @Bean
-ReactiveSessionRegistry reactiveSessionRegistry(WebSessionManager webSessionManager) {
-    return new WebSessionStoreReactiveSessionRegistry(((DefaultWebSessionManager) webSessionManager).getSessionStore());
+ReactiveSessionRegistry reactiveSessionRegistry() {
+    return new InMemoryReactiveSessionRegistry();
 }
 }
 ----
 ----
 
 
@@ -178,8 +179,8 @@ fun maxSessions(): SessionLimit {
 }
 }
 
 
 @Bean
 @Bean
-open fun reactiveSessionRegistry(webSessionManager: WebSessionManager): ReactiveSessionRegistry {
-    return WebSessionStoreReactiveSessionRegistry((webSessionManager as DefaultWebSessionManager).sessionStore)
+open fun reactiveSessionRegistry(): ReactiveSessionRegistry {
+    return InMemoryReactiveSessionRegistry()
 }
 }
 ----
 ----
 ======
 ======
@@ -215,8 +216,8 @@ SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
 }
 }
 
 
 @Bean
 @Bean
-ReactiveSessionRegistry reactiveSessionRegistry(WebSessionManager webSessionManager) {
-    return new WebSessionStoreReactiveSessionRegistry(((DefaultWebSessionManager) webSessionManager).getSessionStore());
+ReactiveSessionRegistry reactiveSessionRegistry() {
+    return new InMemoryReactiveSessionRegistry();
 }
 }
 ----
 ----
 
 
@@ -238,8 +239,8 @@ open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
 }
 }
 
 
 @Bean
 @Bean
-open fun reactiveSessionRegistry(webSessionManager: WebSessionManager): ReactiveSessionRegistry {
-    return WebSessionStoreReactiveSessionRegistry((webSessionManager as DefaultWebSessionManager).sessionStore)
+open fun reactiveSessionRegistry(): ReactiveSessionRegistry {
+    return InMemoryReactiveSessionRegistry()
 }
 }
 ----
 ----
 ======
 ======
@@ -248,15 +249,8 @@ open fun reactiveSessionRegistry(webSessionManager: WebSessionManager): Reactive
 == Specifying a `ReactiveSessionRegistry`
 == 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.
 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`.
-====
+Spring Security ships with {security-api-url}org/springframework/security/core/session/InMemoryReactiveSessionRegistry.html[InMemoryReactiveSessionRegistry] implementation of `ReactiveSessionRegistry`.
 
 
 To specify a `ReactiveSessionRegistry` implementation you can either declare it as a bean:
 To specify a `ReactiveSessionRegistry` implementation you can either declare it as a bean:
 
 
@@ -281,7 +275,7 @@ SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
 
 
 @Bean
 @Bean
 ReactiveSessionRegistry reactiveSessionRegistry() {
 ReactiveSessionRegistry reactiveSessionRegistry() {
-    return new InMemoryReactiveSessionRegistry();
+    return new MyReactiveSessionRegistry();
 }
 }
 ----
 ----
 
 
@@ -303,7 +297,7 @@ open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
 
 
 @Bean
 @Bean
 open fun reactiveSessionRegistry(): ReactiveSessionRegistry {
 open fun reactiveSessionRegistry(): ReactiveSessionRegistry {
-    return InMemoryReactiveSessionRegistry()
+    return MyReactiveSessionRegistry()
 }
 }
 ----
 ----
 ======
 ======
@@ -324,7 +318,7 @@ SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
         .sessionManagement((sessions) -> sessions
         .sessionManagement((sessions) -> sessions
             .concurrentSessions((concurrency) -> concurrency
             .concurrentSessions((concurrency) -> concurrency
                 .maximumSessions(SessionLimit.of(1))
                 .maximumSessions(SessionLimit.of(1))
-                .sessionRegistry(new InMemoryReactiveSessionRegistry())
+                .sessionRegistry(new MyReactiveSessionRegistry())
             )
             )
         );
         );
     return http.build();
     return http.build();
@@ -342,7 +336,7 @@ open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
         sessionManagement {
         sessionManagement {
             sessionConcurrency {
             sessionConcurrency {
                 maximumSessions = SessionLimit.of(1)
                 maximumSessions = SessionLimit.of(1)
-                sessionRegistry = InMemoryReactiveSessionRegistry()
+                sessionRegistry = MyReactiveSessionRegistry()
             }
             }
         }
         }
     }
     }
@@ -355,7 +349,7 @@ open fun springSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
 
 
 At times, it is handy to be able to invalidate all or some of a 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.
 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:
+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
 .Using ReactiveSessionRegistry to invalidate sessions manually
 [tabs]
 [tabs]
@@ -367,13 +361,12 @@ Java::
 public class SessionControl {
 public class SessionControl {
     private final ReactiveSessionRegistry reactiveSessionRegistry;
     private final ReactiveSessionRegistry reactiveSessionRegistry;
 
 
-    public SessionControl(ReactiveSessionRegistry reactiveSessionRegistry) {
-        this.reactiveSessionRegistry = reactiveSessionRegistry;
-    }
+    private final WebSessionStore webSessionStore;
 
 
     public Mono<Void> invalidateSessions(String username) {
     public Mono<Void> invalidateSessions(String username) {
         return this.reactiveSessionRegistry.getAllSessions(username)
         return this.reactiveSessionRegistry.getAllSessions(username)
-            .flatMap(ReactiveSessionInformation::invalidate)
+            .flatMap((session) -> session.invalidate().thenReturn(session))
+            .flatMap((session) -> this.webSessionStore.removeSession(session.getSessionId()))
             .then();
             .then();
     }
     }
 }
 }

+ 6 - 14
web/src/main/java/org/springframework/security/web/server/authentication/ConcurrentSessionControlServerAuthenticationSuccessHandler.java

@@ -45,13 +45,16 @@ public final class ConcurrentSessionControlServerAuthenticationSuccessHandler
 
 
 	private final ReactiveSessionRegistry sessionRegistry;
 	private final ReactiveSessionRegistry sessionRegistry;
 
 
-	private SessionLimit sessionLimit = SessionLimit.of(1);
+	private final ServerMaximumSessionsExceededHandler maximumSessionsExceededHandler;
 
 
-	private ServerMaximumSessionsExceededHandler maximumSessionsExceededHandler = new InvalidateLeastUsedServerMaximumSessionsExceededHandler();
+	private SessionLimit sessionLimit = SessionLimit.of(1);
 
 
-	public ConcurrentSessionControlServerAuthenticationSuccessHandler(ReactiveSessionRegistry sessionRegistry) {
+	public ConcurrentSessionControlServerAuthenticationSuccessHandler(ReactiveSessionRegistry sessionRegistry,
+			ServerMaximumSessionsExceededHandler maximumSessionsExceededHandler) {
 		Assert.notNull(sessionRegistry, "sessionRegistry cannot be null");
 		Assert.notNull(sessionRegistry, "sessionRegistry cannot be null");
+		Assert.notNull(maximumSessionsExceededHandler, "maximumSessionsExceededHandler cannot be null");
 		this.sessionRegistry = sessionRegistry;
 		this.sessionRegistry = sessionRegistry;
+		this.maximumSessionsExceededHandler = maximumSessionsExceededHandler;
 	}
 	}
 
 
 	@Override
 	@Override
@@ -97,15 +100,4 @@ public final class ConcurrentSessionControlServerAuthenticationSuccessHandler
 		this.sessionLimit = sessionLimit;
 		this.sessionLimit = sessionLimit;
 	}
 	}
 
 
-	/**
-	 * Sets the {@link ServerMaximumSessionsExceededHandler} to use. The default is
-	 * {@link InvalidateLeastUsedServerMaximumSessionsExceededHandler}.
-	 * @param maximumSessionsExceededHandler the
-	 * {@link ServerMaximumSessionsExceededHandler} to use
-	 */
-	public void setMaximumSessionsExceededHandler(ServerMaximumSessionsExceededHandler maximumSessionsExceededHandler) {
-		Assert.notNull(maximumSessionsExceededHandler, "maximumSessionsExceededHandler cannot be null");
-		this.maximumSessionsExceededHandler = maximumSessionsExceededHandler;
-	}
-
 }
 }

+ 13 - 12
web/src/main/java/org/springframework/security/web/server/authentication/InvalidateLeastUsedServerMaximumSessionsExceededHandler.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright 2002-2023 the original author or authors.
+ * Copyright 2002-2024 the original author or authors.
  *
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * you may not use this file except in compliance with the License.
@@ -20,19 +20,18 @@ import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.Comparator;
 import java.util.List;
 import java.util.List;
 
 
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 import reactor.core.publisher.Mono;
 
 
-import org.springframework.core.log.LogMessage;
 import org.springframework.security.core.session.ReactiveSessionInformation;
 import org.springframework.security.core.session.ReactiveSessionInformation;
+import org.springframework.web.server.session.WebSessionStore;
 
 
 /**
 /**
  * Implementation of {@link ServerMaximumSessionsExceededHandler} that invalidates the
  * Implementation of {@link ServerMaximumSessionsExceededHandler} that invalidates the
- * least recently used session(s). It only invalidates the amount of sessions that exceed
- * the maximum allowed. For example, if the maximum was exceeded by 1, only the least
- * recently used session will be invalidated.
+ * least recently used {@link ReactiveSessionInformation} and removes the related sessions
+ * from the {@link WebSessionStore}. It only invalidates the amount of sessions that
+ * exceed the maximum allowed. For example, if the maximum was exceeded by 1, only the
+ * least recently used session will be invalidated.
  *
  *
  * @author Marcus da Coregio
  * @author Marcus da Coregio
  * @since 6.3
  * @since 6.3
@@ -40,7 +39,11 @@ import org.springframework.security.core.session.ReactiveSessionInformation;
 public final class InvalidateLeastUsedServerMaximumSessionsExceededHandler
 public final class InvalidateLeastUsedServerMaximumSessionsExceededHandler
 		implements ServerMaximumSessionsExceededHandler {
 		implements ServerMaximumSessionsExceededHandler {
 
 
-	private final Log logger = LogFactory.getLog(getClass());
+	private final WebSessionStore webSessionStore;
+
+	public InvalidateLeastUsedServerMaximumSessionsExceededHandler(WebSessionStore webSessionStore) {
+		this.webSessionStore = webSessionStore;
+	}
 
 
 	@Override
 	@Override
 	public Mono<Void> handle(MaximumSessionsContext context) {
 	public Mono<Void> handle(MaximumSessionsContext context) {
@@ -51,10 +54,8 @@ public final class InvalidateLeastUsedServerMaximumSessionsExceededHandler
 				maximumSessionsExceededBy);
 				maximumSessionsExceededBy);
 
 
 		return Flux.fromIterable(leastRecentlyUsedSessionsToInvalidate)
 		return Flux.fromIterable(leastRecentlyUsedSessionsToInvalidate)
-			.doOnComplete(() -> this.logger
-				.debug(LogMessage.format("Invalidated %d least recently used sessions for authentication %s",
-						leastRecentlyUsedSessionsToInvalidate.size(), context.getAuthentication().getName())))
-			.flatMap(ReactiveSessionInformation::invalidate)
+			.flatMap((toInvalidate) -> toInvalidate.invalidate().thenReturn(toInvalidate))
+			.flatMap((toInvalidate) -> this.webSessionStore.removeSession(toInvalidate.getSessionId()))
 			.then();
 			.then();
 	}
 	}
 
 

+ 0 - 100
web/src/main/java/org/springframework/security/web/session/WebSessionStoreReactiveSessionRegistry.java

@@ -1,100 +0,0 @@
-/*
- * Copyright 2002-2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.security.web.session;
-
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.Mono;
-
-import org.springframework.security.core.session.InMemoryReactiveSessionRegistry;
-import org.springframework.security.core.session.ReactiveSessionInformation;
-import org.springframework.security.core.session.ReactiveSessionRegistry;
-import org.springframework.util.Assert;
-import org.springframework.web.server.WebSession;
-import org.springframework.web.server.session.WebSessionStore;
-
-/**
- * A {@link ReactiveSessionRegistry} implementation that uses a {@link WebSessionStore} to
- * invalidate a {@link WebSession} when the {@link ReactiveSessionInformation} is
- * invalidated.
- *
- * @author Marcus da Coregio
- * @since 6.3
- */
-public final class WebSessionStoreReactiveSessionRegistry implements ReactiveSessionRegistry {
-
-	private final WebSessionStore webSessionStore;
-
-	private ReactiveSessionRegistry sessionRegistry = new InMemoryReactiveSessionRegistry();
-
-	public WebSessionStoreReactiveSessionRegistry(WebSessionStore webSessionStore) {
-		Assert.notNull(webSessionStore, "webSessionStore cannot be null");
-		this.webSessionStore = webSessionStore;
-	}
-
-	@Override
-	public Flux<ReactiveSessionInformation> getAllSessions(Object principal) {
-		return this.sessionRegistry.getAllSessions(principal).map(WebSessionInformation::new);
-	}
-
-	@Override
-	public Mono<Void> saveSessionInformation(ReactiveSessionInformation information) {
-		return this.sessionRegistry.saveSessionInformation(new WebSessionInformation(information));
-	}
-
-	@Override
-	public Mono<ReactiveSessionInformation> getSessionInformation(String sessionId) {
-		return this.sessionRegistry.getSessionInformation(sessionId).map(WebSessionInformation::new);
-	}
-
-	@Override
-	public Mono<ReactiveSessionInformation> removeSessionInformation(String sessionId) {
-		return this.sessionRegistry.removeSessionInformation(sessionId).map(WebSessionInformation::new);
-	}
-
-	@Override
-	public Mono<ReactiveSessionInformation> updateLastAccessTime(String sessionId) {
-		return this.sessionRegistry.updateLastAccessTime(sessionId).map(WebSessionInformation::new);
-	}
-
-	/**
-	 * Sets the {@link ReactiveSessionRegistry} to use.
-	 * @param sessionRegistry the {@link ReactiveSessionRegistry} to use. Cannot be null.
-	 */
-	public void setSessionRegistry(ReactiveSessionRegistry sessionRegistry) {
-		Assert.notNull(sessionRegistry, "sessionRegistry cannot be null");
-		this.sessionRegistry = sessionRegistry;
-	}
-
-	final class WebSessionInformation extends ReactiveSessionInformation {
-
-		WebSessionInformation(ReactiveSessionInformation sessionInformation) {
-			super(sessionInformation.getPrincipal(), sessionInformation.getSessionId(),
-					sessionInformation.getLastAccessTime());
-		}
-
-		@Override
-		public Mono<Void> invalidate() {
-			return WebSessionStoreReactiveSessionRegistry.this.webSessionStore.retrieveSession(getSessionId())
-				.flatMap(WebSession::invalidate)
-				.then(Mono
-					.defer(() -> WebSessionStoreReactiveSessionRegistry.this.removeSessionInformation(getSessionId())))
-				.then(Mono.defer(super::invalidate));
-		}
-
-	}
-
-}

+ 12 - 10
web/src/test/java/org/springframework/security/web/server/authentication/session/ConcurrentSessionControlServerAuthenticationSuccessHandlerTests.java

@@ -74,34 +74,36 @@ class ConcurrentSessionControlServerAuthenticationSuccessHandlerTests {
 		given(this.exchange.getRequest()).willReturn(MockServerHttpRequest.get("/").build());
 		given(this.exchange.getRequest()).willReturn(MockServerHttpRequest.get("/").build());
 		given(this.exchange.getSession()).willReturn(Mono.just(new MockWebSession()));
 		given(this.exchange.getSession()).willReturn(Mono.just(new MockWebSession()));
 		given(this.handler.handle(any())).willReturn(Mono.empty());
 		given(this.handler.handle(any())).willReturn(Mono.empty());
-		this.strategy = new ConcurrentSessionControlServerAuthenticationSuccessHandler(this.sessionRegistry);
-		this.strategy.setMaximumSessionsExceededHandler(this.handler);
+		this.strategy = new ConcurrentSessionControlServerAuthenticationSuccessHandler(this.sessionRegistry,
+				this.handler);
 	}
 	}
 
 
 	@Test
 	@Test
 	void constructorWhenNullRegistryThenException() {
 	void constructorWhenNullRegistryThenException() {
 		assertThatIllegalArgumentException()
 		assertThatIllegalArgumentException()
-			.isThrownBy(() -> new ConcurrentSessionControlServerAuthenticationSuccessHandler(null))
+			.isThrownBy(() -> new ConcurrentSessionControlServerAuthenticationSuccessHandler(null, this.handler))
 			.withMessage("sessionRegistry cannot be null");
 			.withMessage("sessionRegistry cannot be null");
 	}
 	}
 
 
 	@Test
 	@Test
-	void setMaximumSessionsForAuthenticationWhenNullThenException() {
-		assertThatIllegalArgumentException().isThrownBy(() -> this.strategy.setSessionLimit(null))
-			.withMessage("sessionLimit cannot be null");
+	void constructorWhenNullHandlerThenException() {
+		assertThatIllegalArgumentException()
+			.isThrownBy(
+					() -> new ConcurrentSessionControlServerAuthenticationSuccessHandler(this.sessionRegistry, null))
+			.withMessage("maximumSessionsExceededHandler cannot be null");
 	}
 	}
 
 
 	@Test
 	@Test
-	void setMaximumSessionsExceededHandlerWhenNullThenException() {
-		assertThatIllegalArgumentException().isThrownBy(() -> this.strategy.setMaximumSessionsExceededHandler(null))
-			.withMessage("maximumSessionsExceededHandler cannot be null");
+	void setMaximumSessionsForAuthenticationWhenNullThenException() {
+		assertThatIllegalArgumentException().isThrownBy(() -> this.strategy.setSessionLimit(null))
+			.withMessage("sessionLimit cannot be null");
 	}
 	}
 
 
 	@Test
 	@Test
 	void onAuthenticationWhenSessionLimitIsUnlimitedThenDoNothing() {
 	void onAuthenticationWhenSessionLimitIsUnlimitedThenDoNothing() {
 		ServerMaximumSessionsExceededHandler handler = mock(ServerMaximumSessionsExceededHandler.class);
 		ServerMaximumSessionsExceededHandler handler = mock(ServerMaximumSessionsExceededHandler.class);
+		this.strategy = new ConcurrentSessionControlServerAuthenticationSuccessHandler(this.sessionRegistry, handler);
 		this.strategy.setSessionLimit(SessionLimit.UNLIMITED);
 		this.strategy.setSessionLimit(SessionLimit.UNLIMITED);
-		this.strategy.setMaximumSessionsExceededHandler(handler);
 		this.strategy.onAuthenticationSuccess(null, TestAuthentication.authenticatedUser()).block();
 		this.strategy.onAuthenticationSuccess(null, TestAuthentication.authenticatedUser()).block();
 		verifyNoInteractions(handler, this.sessionRegistry);
 		verifyNoInteractions(handler, this.sessionRegistry);
 	}
 	}

+ 26 - 2
web/src/test/java/org/springframework/security/web/server/authentication/session/InvalidateLeastUsedServerMaximumSessionsExceededHandlerTests.java

@@ -19,6 +19,7 @@ package org.springframework.security.web.server.authentication.session;
 import java.time.Instant;
 import java.time.Instant;
 import java.util.List;
 import java.util.List;
 
 
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Test;
 import reactor.core.publisher.Mono;
 import reactor.core.publisher.Mono;
 
 
@@ -26,10 +27,13 @@ import org.springframework.security.core.Authentication;
 import org.springframework.security.core.session.ReactiveSessionInformation;
 import org.springframework.security.core.session.ReactiveSessionInformation;
 import org.springframework.security.web.server.authentication.InvalidateLeastUsedServerMaximumSessionsExceededHandler;
 import org.springframework.security.web.server.authentication.InvalidateLeastUsedServerMaximumSessionsExceededHandler;
 import org.springframework.security.web.server.authentication.MaximumSessionsContext;
 import org.springframework.security.web.server.authentication.MaximumSessionsContext;
+import org.springframework.web.server.session.InMemoryWebSessionStore;
+import org.springframework.web.server.session.WebSessionStore;
 
 
 import static org.mockito.BDDMockito.given;
 import static org.mockito.BDDMockito.given;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 
@@ -40,7 +44,14 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
  */
  */
 class InvalidateLeastUsedServerMaximumSessionsExceededHandlerTests {
 class InvalidateLeastUsedServerMaximumSessionsExceededHandlerTests {
 
 
-	InvalidateLeastUsedServerMaximumSessionsExceededHandler handler = new InvalidateLeastUsedServerMaximumSessionsExceededHandler();
+	InvalidateLeastUsedServerMaximumSessionsExceededHandler handler;
+
+	WebSessionStore webSessionStore = spy(new InMemoryWebSessionStore());
+
+	@BeforeEach
+	void setup() {
+		this.handler = new InvalidateLeastUsedServerMaximumSessionsExceededHandler(this.webSessionStore);
+	}
 
 
 	@Test
 	@Test
 	void handleWhenInvokedThenInvalidatesLeastRecentlyUsedSessions() {
 	void handleWhenInvokedThenInvalidatesLeastRecentlyUsedSessions() {
@@ -48,7 +59,9 @@ class InvalidateLeastUsedServerMaximumSessionsExceededHandlerTests {
 		ReactiveSessionInformation session2 = mock(ReactiveSessionInformation.class);
 		ReactiveSessionInformation session2 = mock(ReactiveSessionInformation.class);
 		given(session1.getLastAccessTime()).willReturn(Instant.ofEpochMilli(1700827760010L));
 		given(session1.getLastAccessTime()).willReturn(Instant.ofEpochMilli(1700827760010L));
 		given(session2.getLastAccessTime()).willReturn(Instant.ofEpochMilli(1700827760000L));
 		given(session2.getLastAccessTime()).willReturn(Instant.ofEpochMilli(1700827760000L));
+		given(session2.getSessionId()).willReturn("session2");
 		given(session2.invalidate()).willReturn(Mono.empty());
 		given(session2.invalidate()).willReturn(Mono.empty());
+
 		MaximumSessionsContext context = new MaximumSessionsContext(mock(Authentication.class),
 		MaximumSessionsContext context = new MaximumSessionsContext(mock(Authentication.class),
 				List.of(session1, session2), 2, null);
 				List.of(session1, session2), 2, null);
 
 
@@ -57,6 +70,10 @@ class InvalidateLeastUsedServerMaximumSessionsExceededHandlerTests {
 		verify(session2).invalidate();
 		verify(session2).invalidate();
 		verify(session1).getLastAccessTime(); // used by comparator to sort the sessions
 		verify(session1).getLastAccessTime(); // used by comparator to sort the sessions
 		verify(session2).getLastAccessTime(); // used by comparator to sort the sessions
 		verify(session2).getLastAccessTime(); // used by comparator to sort the sessions
+		verify(session2).getSessionId(); // used to invalidate session against the
+		// WebSessionStore
+		verify(this.webSessionStore).removeSession("session2");
+		verifyNoMoreInteractions(this.webSessionStore);
 		verifyNoMoreInteractions(session2);
 		verifyNoMoreInteractions(session2);
 		verifyNoMoreInteractions(session1);
 		verifyNoMoreInteractions(session1);
 	}
 	}
@@ -71,17 +88,24 @@ class InvalidateLeastUsedServerMaximumSessionsExceededHandlerTests {
 		given(session3.getLastAccessTime()).willReturn(Instant.ofEpochMilli(1700827760030L));
 		given(session3.getLastAccessTime()).willReturn(Instant.ofEpochMilli(1700827760030L));
 		given(session1.invalidate()).willReturn(Mono.empty());
 		given(session1.invalidate()).willReturn(Mono.empty());
 		given(session2.invalidate()).willReturn(Mono.empty());
 		given(session2.invalidate()).willReturn(Mono.empty());
+		given(session1.getSessionId()).willReturn("session1");
+		given(session2.getSessionId()).willReturn("session2");
+
 		MaximumSessionsContext context = new MaximumSessionsContext(mock(Authentication.class),
 		MaximumSessionsContext context = new MaximumSessionsContext(mock(Authentication.class),
 				List.of(session1, session2, session3), 2, null);
 				List.of(session1, session2, session3), 2, null);
-
 		this.handler.handle(context).block();
 		this.handler.handle(context).block();
 
 
 		// @formatter:off
 		// @formatter:off
 		verify(session1).invalidate();
 		verify(session1).invalidate();
 		verify(session2).invalidate();
 		verify(session2).invalidate();
+		verify(session1).getSessionId();
+		verify(session2).getSessionId();
 		verify(session1, atLeastOnce()).getLastAccessTime(); // used by comparator to sort the sessions
 		verify(session1, atLeastOnce()).getLastAccessTime(); // used by comparator to sort the sessions
 		verify(session2, atLeastOnce()).getLastAccessTime(); // used by comparator to sort the sessions
 		verify(session2, atLeastOnce()).getLastAccessTime(); // used by comparator to sort the sessions
 		verify(session3, atLeastOnce()).getLastAccessTime(); // used by comparator to sort the sessions
 		verify(session3, atLeastOnce()).getLastAccessTime(); // used by comparator to sort the sessions
+		verify(this.webSessionStore).removeSession("session1");
+		verify(this.webSessionStore).removeSession("session2");
+		verifyNoMoreInteractions(this.webSessionStore);
 		verifyNoMoreInteractions(session1);
 		verifyNoMoreInteractions(session1);
 		verifyNoMoreInteractions(session2);
 		verifyNoMoreInteractions(session2);
 		verifyNoMoreInteractions(session3);
 		verifyNoMoreInteractions(session3);

+ 0 - 136
web/src/test/java/org/springframework/security/web/session/WebSessionStoreReactiveSessionRegistryTests.java

@@ -1,136 +0,0 @@
-/*
- * Copyright 2002-2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.security.web.session;
-
-import java.time.Instant;
-import java.util.List;
-
-import org.junit.jupiter.api.Test;
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.Mono;
-
-import org.springframework.security.core.session.ReactiveSessionInformation;
-import org.springframework.security.core.session.ReactiveSessionRegistry;
-import org.springframework.web.server.WebSession;
-import org.springframework.web.server.session.WebSessionStore;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.BDDMockito.given;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-/**
- * Tests for {@link WebSessionStoreReactiveSessionRegistry}
- *
- * @author Marcus da Coregio
- */
-class WebSessionStoreReactiveSessionRegistryTests {
-
-	WebSessionStore webSessionStore = mock();
-
-	WebSessionStoreReactiveSessionRegistry registry = new WebSessionStoreReactiveSessionRegistry(this.webSessionStore);
-
-	@Test
-	void constructorWhenWebSessionStoreNullThenException() {
-		assertThatIllegalArgumentException().isThrownBy(() -> new WebSessionStoreReactiveSessionRegistry(null))
-			.withMessage("webSessionStore cannot be null");
-	}
-
-	@Test
-	void getSessionInformationWhenSavedThenReturnsWebSessionInformation() {
-		ReactiveSessionInformation session = createSession();
-		this.registry.saveSessionInformation(session).block();
-		ReactiveSessionInformation saved = this.registry.getSessionInformation(session.getSessionId()).block();
-		assertThat(saved).isInstanceOf(WebSessionStoreReactiveSessionRegistry.WebSessionInformation.class);
-		assertThat(saved.getPrincipal()).isEqualTo(session.getPrincipal());
-		assertThat(saved.getSessionId()).isEqualTo(session.getSessionId());
-		assertThat(saved.getLastAccessTime()).isEqualTo(session.getLastAccessTime());
-	}
-
-	@Test
-	void invalidateWhenReturnedFromGetSessionInformationThenWebSessionInvalidatedAndRemovedFromRegistry() {
-		ReactiveSessionInformation session = createSession();
-		WebSession webSession = mock();
-		given(webSession.invalidate()).willReturn(Mono.empty());
-		given(this.webSessionStore.retrieveSession(session.getSessionId())).willReturn(Mono.just(webSession));
-
-		this.registry.saveSessionInformation(session).block();
-		ReactiveSessionInformation saved = this.registry.getSessionInformation(session.getSessionId()).block();
-		saved.invalidate().block();
-		verify(webSession).invalidate();
-		assertThat(this.registry.getSessionInformation(saved.getSessionId()).block()).isNull();
-	}
-
-	@Test
-	void invalidateWhenReturnedFromRemoveSessionInformationThenWebSessionInvalidatedAndRemovedFromRegistry() {
-		ReactiveSessionInformation session = createSession();
-		WebSession webSession = mock();
-		given(webSession.invalidate()).willReturn(Mono.empty());
-		given(this.webSessionStore.retrieveSession(session.getSessionId())).willReturn(Mono.just(webSession));
-
-		this.registry.saveSessionInformation(session).block();
-		ReactiveSessionInformation saved = this.registry.removeSessionInformation(session.getSessionId()).block();
-		saved.invalidate().block();
-		verify(webSession).invalidate();
-		assertThat(this.registry.getSessionInformation(saved.getSessionId()).block()).isNull();
-	}
-
-	@Test
-	void invalidateWhenReturnedFromGetAllSessionsThenWebSessionInvalidatedAndRemovedFromRegistry() {
-		ReactiveSessionInformation session = createSession();
-		WebSession webSession = mock();
-		given(webSession.invalidate()).willReturn(Mono.empty());
-		given(this.webSessionStore.retrieveSession(session.getSessionId())).willReturn(Mono.just(webSession));
-
-		this.registry.saveSessionInformation(session).block();
-		List<ReactiveSessionInformation> saved = this.registry.getAllSessions(session.getPrincipal())
-			.collectList()
-			.block();
-		saved.forEach((info) -> info.invalidate().block());
-		verify(webSession).invalidate();
-		assertThat(this.registry.getAllSessions(session.getPrincipal()).collectList().block()).isEmpty();
-	}
-
-	@Test
-	void setSessionRegistryThenUses() {
-		ReactiveSessionRegistry sessionRegistry = mock();
-		given(sessionRegistry.saveSessionInformation(any())).willReturn(Mono.empty());
-		given(sessionRegistry.removeSessionInformation(any())).willReturn(Mono.empty());
-		given(sessionRegistry.updateLastAccessTime(any())).willReturn(Mono.empty());
-		given(sessionRegistry.getSessionInformation(any())).willReturn(Mono.empty());
-		given(sessionRegistry.getAllSessions(any())).willReturn(Flux.empty());
-		this.registry.setSessionRegistry(sessionRegistry);
-		ReactiveSessionInformation session = createSession();
-		this.registry.saveSessionInformation(session).block();
-		verify(sessionRegistry).saveSessionInformation(any());
-		this.registry.removeSessionInformation(session.getSessionId()).block();
-		verify(sessionRegistry).removeSessionInformation(any());
-		this.registry.updateLastAccessTime(session.getSessionId()).block();
-		verify(sessionRegistry).updateLastAccessTime(any());
-		this.registry.getSessionInformation(session.getSessionId()).block();
-		verify(sessionRegistry).getSessionInformation(any());
-		this.registry.getAllSessions(session.getPrincipal()).blockFirst();
-		verify(sessionRegistry).getAllSessions(any());
-	}
-
-	private static ReactiveSessionInformation createSession() {
-		return new ReactiveSessionInformation("principal", "sessionId", Instant.now());
-	}
-
-}