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
This commit is contained in:
@@ -74,34 +74,36 @@ class ConcurrentSessionControlServerAuthenticationSuccessHandlerTests {
|
||||
given(this.exchange.getRequest()).willReturn(MockServerHttpRequest.get("/").build());
|
||||
given(this.exchange.getSession()).willReturn(Mono.just(new MockWebSession()));
|
||||
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
|
||||
void constructorWhenNullRegistryThenException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new ConcurrentSessionControlServerAuthenticationSuccessHandler(null))
|
||||
.isThrownBy(() -> new ConcurrentSessionControlServerAuthenticationSuccessHandler(null, this.handler))
|
||||
.withMessage("sessionRegistry cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructorWhenNullHandlerThenException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(
|
||||
() -> new ConcurrentSessionControlServerAuthenticationSuccessHandler(this.sessionRegistry, null))
|
||||
.withMessage("maximumSessionsExceededHandler cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setMaximumSessionsForAuthenticationWhenNullThenException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.strategy.setSessionLimit(null))
|
||||
.withMessage("sessionLimit cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setMaximumSessionsExceededHandlerWhenNullThenException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.strategy.setMaximumSessionsExceededHandler(null))
|
||||
.withMessage("maximumSessionsExceededHandler cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void onAuthenticationWhenSessionLimitIsUnlimitedThenDoNothing() {
|
||||
ServerMaximumSessionsExceededHandler handler = mock(ServerMaximumSessionsExceededHandler.class);
|
||||
this.strategy = new ConcurrentSessionControlServerAuthenticationSuccessHandler(this.sessionRegistry, handler);
|
||||
this.strategy.setSessionLimit(SessionLimit.UNLIMITED);
|
||||
this.strategy.setMaximumSessionsExceededHandler(handler);
|
||||
this.strategy.onAuthenticationSuccess(null, TestAuthentication.authenticatedUser()).block();
|
||||
verifyNoInteractions(handler, this.sessionRegistry);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.security.web.server.authentication.session;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
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.web.server.authentication.InvalidateLeastUsedServerMaximumSessionsExceededHandler;
|
||||
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.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
@@ -40,7 +44,14 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
*/
|
||||
class InvalidateLeastUsedServerMaximumSessionsExceededHandlerTests {
|
||||
|
||||
InvalidateLeastUsedServerMaximumSessionsExceededHandler handler = new InvalidateLeastUsedServerMaximumSessionsExceededHandler();
|
||||
InvalidateLeastUsedServerMaximumSessionsExceededHandler handler;
|
||||
|
||||
WebSessionStore webSessionStore = spy(new InMemoryWebSessionStore());
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.handler = new InvalidateLeastUsedServerMaximumSessionsExceededHandler(this.webSessionStore);
|
||||
}
|
||||
|
||||
@Test
|
||||
void handleWhenInvokedThenInvalidatesLeastRecentlyUsedSessions() {
|
||||
@@ -48,7 +59,9 @@ class InvalidateLeastUsedServerMaximumSessionsExceededHandlerTests {
|
||||
ReactiveSessionInformation session2 = mock(ReactiveSessionInformation.class);
|
||||
given(session1.getLastAccessTime()).willReturn(Instant.ofEpochMilli(1700827760010L));
|
||||
given(session2.getLastAccessTime()).willReturn(Instant.ofEpochMilli(1700827760000L));
|
||||
given(session2.getSessionId()).willReturn("session2");
|
||||
given(session2.invalidate()).willReturn(Mono.empty());
|
||||
|
||||
MaximumSessionsContext context = new MaximumSessionsContext(mock(Authentication.class),
|
||||
List.of(session1, session2), 2, null);
|
||||
|
||||
@@ -57,6 +70,10 @@ class InvalidateLeastUsedServerMaximumSessionsExceededHandlerTests {
|
||||
verify(session2).invalidate();
|
||||
verify(session1).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(session1);
|
||||
}
|
||||
@@ -71,17 +88,24 @@ class InvalidateLeastUsedServerMaximumSessionsExceededHandlerTests {
|
||||
given(session3.getLastAccessTime()).willReturn(Instant.ofEpochMilli(1700827760030L));
|
||||
given(session1.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),
|
||||
List.of(session1, session2, session3), 2, null);
|
||||
|
||||
this.handler.handle(context).block();
|
||||
|
||||
// @formatter:off
|
||||
verify(session1).invalidate();
|
||||
verify(session2).invalidate();
|
||||
verify(session1).getSessionId();
|
||||
verify(session2).getSessionId();
|
||||
verify(session1, 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(this.webSessionStore).removeSession("session1");
|
||||
verify(this.webSessionStore).removeSession("session2");
|
||||
verifyNoMoreInteractions(this.webSessionStore);
|
||||
verifyNoMoreInteractions(session1);
|
||||
verifyNoMoreInteractions(session2);
|
||||
verifyNoMoreInteractions(session3);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user