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:
Marcus Hert Da Coregio
2024-02-28 10:06:45 -03:00
parent f3bcf7ed5d
commit f8ff056eb6
10 changed files with 331 additions and 580 deletions

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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());
}
}