Skip to content

Commit 927840f

Browse files
joaquinjsbmarcusdacoregio
authored andcommitted
Do Not Invalidate Current Session When It Is Registered
Closes gh-15066
1 parent e321e5d commit 927840f

File tree

2 files changed

+44
-5
lines changed

2 files changed

+44
-5
lines changed

web/src/main/java/org/springframework/security/web/server/authentication/InvalidateLeastUsedServerMaximumSessionsExceededHandler.java

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import reactor.core.publisher.Mono;
2525

2626
import org.springframework.security.core.session.ReactiveSessionInformation;
27+
import org.springframework.util.Assert;
2728
import org.springframework.web.server.session.WebSessionStore;
2829

2930
/**
@@ -42,12 +43,14 @@ public final class InvalidateLeastUsedServerMaximumSessionsExceededHandler
4243
private final WebSessionStore webSessionStore;
4344

4445
public InvalidateLeastUsedServerMaximumSessionsExceededHandler(WebSessionStore webSessionStore) {
46+
Assert.notNull(webSessionStore, "webSessionStore cannot be null");
4547
this.webSessionStore = webSessionStore;
4648
}
4749

4850
@Override
4951
public Mono<Void> handle(MaximumSessionsContext context) {
5052
List<ReactiveSessionInformation> sessions = new ArrayList<>(context.getSessions());
53+
sessions.removeIf((session) -> session.getSessionId().equals(context.getCurrentSession().getId()));
5154
sessions.sort(Comparator.comparing(ReactiveSessionInformation::getLastAccessTime));
5255
int maximumSessionsExceededBy = sessions.size() - context.getMaximumSessionsAllowed() + 1;
5356
List<ReactiveSessionInformation> leastRecentlyUsedSessionsToInvalidate = sessions.subList(0,

web/src/test/java/org/springframework/security/web/server/authentication/session/InvalidateLeastUsedServerMaximumSessionsExceededHandlerTests.java

+41-5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.junit.jupiter.api.Test;
2424
import reactor.core.publisher.Mono;
2525

26+
import org.springframework.mock.web.server.MockWebSession;
2627
import org.springframework.security.core.Authentication;
2728
import org.springframework.security.core.session.ReactiveSessionInformation;
2829
import org.springframework.security.web.server.authentication.InvalidateLeastUsedServerMaximumSessionsExceededHandler;
@@ -34,6 +35,7 @@
3435
import static org.mockito.Mockito.atLeastOnce;
3536
import static org.mockito.Mockito.mock;
3637
import static org.mockito.Mockito.spy;
38+
import static org.mockito.Mockito.times;
3739
import static org.mockito.Mockito.verify;
3840
import static org.mockito.Mockito.verifyNoMoreInteractions;
3941

@@ -59,18 +61,21 @@ void handleWhenInvokedThenInvalidatesLeastRecentlyUsedSessions() {
5961
ReactiveSessionInformation session2 = mock(ReactiveSessionInformation.class);
6062
given(session1.getLastAccessTime()).willReturn(Instant.ofEpochMilli(1700827760010L));
6163
given(session2.getLastAccessTime()).willReturn(Instant.ofEpochMilli(1700827760000L));
64+
given(session1.getSessionId()).willReturn("session1");
6265
given(session2.getSessionId()).willReturn("session2");
6366
given(session2.invalidate()).willReturn(Mono.empty());
6467

6568
MaximumSessionsContext context = new MaximumSessionsContext(mock(Authentication.class),
66-
List.of(session1, session2), 2, null);
69+
List.of(session1, session2), 2, createWebSession());
6770

6871
this.handler.handle(context).block();
6972

7073
verify(session2).invalidate();
7174
verify(session1).getLastAccessTime(); // used by comparator to sort the sessions
7275
verify(session2).getLastAccessTime(); // used by comparator to sort the sessions
73-
verify(session2).getSessionId(); // used to invalidate session against the
76+
verify(session1).getSessionId();
77+
verify(session2, times(2)).getSessionId(); // used to invalidate session against
78+
// the
7479
// WebSessionStore
7580
verify(this.webSessionStore).removeSession("session2");
7681
verifyNoMoreInteractions(this.webSessionStore);
@@ -90,16 +95,18 @@ void handleWhenMoreThanOneSessionToInvalidateThenInvalidatesAllOfThem() {
9095
given(session2.invalidate()).willReturn(Mono.empty());
9196
given(session1.getSessionId()).willReturn("session1");
9297
given(session2.getSessionId()).willReturn("session2");
98+
given(session3.getSessionId()).willReturn("session3");
9399

94100
MaximumSessionsContext context = new MaximumSessionsContext(mock(Authentication.class),
95-
List.of(session1, session2, session3), 2, null);
101+
List.of(session1, session2, session3), 2, createWebSession());
96102
this.handler.handle(context).block();
97103

98104
// @formatter:off
99105
verify(session1).invalidate();
100106
verify(session2).invalidate();
101-
verify(session1).getSessionId();
102-
verify(session2).getSessionId();
107+
verify(session1, times(2)).getSessionId();
108+
verify(session2, times(2)).getSessionId();
109+
verify(session3).getSessionId();
103110
verify(session1, atLeastOnce()).getLastAccessTime(); // used by comparator to sort the sessions
104111
verify(session2, atLeastOnce()).getLastAccessTime(); // used by comparator to sort the sessions
105112
verify(session3, atLeastOnce()).getLastAccessTime(); // used by comparator to sort the sessions
@@ -112,4 +119,33 @@ void handleWhenMoreThanOneSessionToInvalidateThenInvalidatesAllOfThem() {
112119
// @formatter:on
113120
}
114121

122+
@Test
123+
void handleWhenCurrentSessionIsRegisteredThenDoNotInvalidateCurrentSession() {
124+
ReactiveSessionInformation session1 = mock(ReactiveSessionInformation.class);
125+
ReactiveSessionInformation session2 = mock(ReactiveSessionInformation.class);
126+
MockWebSession currentSession = createWebSession();
127+
given(session1.getLastAccessTime()).willReturn(Instant.ofEpochMilli(1700827760010L));
128+
given(session2.getLastAccessTime()).willReturn(Instant.ofEpochMilli(1700827760000L));
129+
given(session1.getSessionId()).willReturn("session1");
130+
given(session2.getSessionId()).willReturn(currentSession.getId());
131+
given(session1.invalidate()).willReturn(Mono.empty());
132+
133+
MaximumSessionsContext context = new MaximumSessionsContext(mock(Authentication.class),
134+
List.of(session1, session2), 1, currentSession);
135+
136+
this.handler.handle(context).block();
137+
138+
verify(session1).invalidate();
139+
verify(session2).getSessionId();
140+
verify(session1, times(2)).getSessionId();
141+
verify(this.webSessionStore).removeSession("session1");
142+
verifyNoMoreInteractions(this.webSessionStore);
143+
verifyNoMoreInteractions(session2);
144+
verifyNoMoreInteractions(session1);
145+
}
146+
147+
private MockWebSession createWebSession() {
148+
return new MockWebSession();
149+
}
150+
115151
}

0 commit comments

Comments
 (0)