Skip to content

Commit cb2decc

Browse files
committed
WebSessionStore performs expiration check on retrieve
Issue: SPR-15963
1 parent fbb428f commit cb2decc

File tree

6 files changed

+85
-42
lines changed

6 files changed

+85
-42
lines changed

spring-web/src/main/java/org/springframework/web/server/session/DefaultWebSessionManager.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ public WebSessionStore getSessionStore() {
8080
public Mono<WebSession> getSession(ServerWebExchange exchange) {
8181
return Mono.defer(() ->
8282
retrieveSession(exchange)
83-
.flatMap(session -> removeSessionIfExpired(exchange, session))
8483
.flatMap(this.getSessionStore()::updateLastAccessTime)
8584
.switchIfEmpty(this.sessionStore.createWebSession())
8685
.doOnNext(session -> exchange.getResponse().beforeCommit(() -> save(exchange, session))));
@@ -92,14 +91,6 @@ private Mono<WebSession> retrieveSession(ServerWebExchange exchange) {
9291
.next();
9392
}
9493

95-
private Mono<WebSession> removeSessionIfExpired(ServerWebExchange exchange, WebSession session) {
96-
if (session.isExpired()) {
97-
this.sessionIdResolver.expireSession(exchange);
98-
return this.sessionStore.removeSession(session.getId()).then(Mono.empty());
99-
}
100-
return Mono.just(session);
101-
}
102-
10394
private Mono<Void> save(ServerWebExchange exchange, WebSession session) {
10495
if (session.isExpired()) {
10596
return Mono.error(new IllegalStateException(
@@ -110,11 +101,14 @@ private Mono<Void> save(ServerWebExchange exchange, WebSession session) {
110101
}
111102

112103
if (!session.isStarted()) {
104+
if (hasNewSessionId(exchange, session)) {
105+
this.sessionIdResolver.expireSession(exchange);
106+
}
113107
return Mono.empty();
114108
}
115109

116110
if (hasNewSessionId(exchange, session)) {
117-
DefaultWebSessionManager.this.sessionIdResolver.setSessionId(exchange, session.getId());
111+
this.sessionIdResolver.setSessionId(exchange, session.getId());
118112
}
119113

120114
return session.save();

spring-web/src/main/java/org/springframework/web/server/session/InMemoryWebSessionStore.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,17 @@ public Mono<WebSession> createWebSession() {
7777

7878
@Override
7979
public Mono<WebSession> retrieveSession(String id) {
80-
return (this.sessions.containsKey(id) ? Mono.just(this.sessions.get(id)) : Mono.empty());
80+
WebSession session = this.sessions.get(id);
81+
if (session == null) {
82+
return Mono.empty();
83+
}
84+
else if (session.isExpired()) {
85+
this.sessions.remove(id);
86+
return Mono.empty();
87+
}
88+
else {
89+
return Mono.just(session);
90+
}
8191
}
8292

8393
@Override

spring-web/src/main/java/org/springframework/web/server/session/WebSessionStore.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ public interface WebSessionStore {
4040

4141
/**
4242
* Return the WebSession for the given id.
43+
* <p><strong>Note:</strong> This method should perform an expiration check,
44+
* remove the session if it has expired and return empty.
4345
* @param sessionId the session to load
44-
* @return the session, or an empty {@code Mono}.
46+
* @return the session, or an empty {@code Mono} .
4547
*/
4648
Mono<WebSession> retrieveSession(String sessionId);
4749

spring-web/src/test/java/org/springframework/web/server/session/DefaultWebSessionManagerTests.java

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
import static org.mockito.ArgumentMatchers.eq;
4141
import static org.mockito.Mockito.never;
4242
import static org.mockito.Mockito.verify;
43-
import static org.mockito.Mockito.verifyZeroInteractions;
4443
import static org.mockito.Mockito.when;
4544

4645
/**
@@ -64,20 +63,16 @@ public class DefaultWebSessionManagerTests {
6463
@Mock
6564
private WebSession createSession;
6665

67-
@Mock
68-
private WebSession retrieveSession;
69-
7066
@Mock
7167
private WebSession updateSession;
7268

69+
7370
@Before
7471
public void setUp() throws Exception {
7572
when(this.store.createWebSession()).thenReturn(Mono.just(this.createSession));
7673
when(this.store.updateLastAccessTime(any())).thenReturn(Mono.just(this.updateSession));
77-
when(this.store.retrieveSession(any())).thenReturn(Mono.just(this.retrieveSession));
7874
when(this.createSession.save()).thenReturn(Mono.empty());
7975
when(this.updateSession.getId()).thenReturn("update-session-id");
80-
when(this.retrieveSession.getId()).thenReturn("retrieve-session-id");
8176

8277
this.manager = new DefaultWebSessionManager();
8378
this.manager.setSessionIdResolver(this.idResolver);
@@ -97,7 +92,6 @@ public void getSessionSaveWhenCreatedAndNotStartedThenNotSaved() throws Exceptio
9792

9893
assertFalse(session.isStarted());
9994
assertFalse(session.isExpired());
100-
verifyZeroInteractions(this.retrieveSession, this.updateSession);
10195
verify(this.createSession, never()).save();
10296
verify(this.idResolver, never()).setSessionId(any(), any());
10397
}
@@ -138,19 +132,6 @@ public void existingSession() throws Exception {
138132
assertEquals(id, actual.getId());
139133
}
140134

141-
@Test
142-
public void existingSessionIsExpired() throws Exception {
143-
String id = this.retrieveSession.getId();
144-
when(this.retrieveSession.isExpired()).thenReturn(true);
145-
when(this.idResolver.resolveSessionIds(this.exchange)).thenReturn(Collections.singletonList(id));
146-
when(this.store.removeSession(any())).thenReturn(Mono.empty());
147-
148-
WebSession actual = this.manager.getSession(this.exchange).block();
149-
assertEquals(this.createSession.getId(), actual.getId());
150-
verify(this.store).removeSession(id);
151-
verify(this.idResolver).expireSession(any());
152-
}
153-
154135
@Test
155136
public void multipleSessionIds() throws Exception {
156137
WebSession existing = this.updateSession;

spring-web/src/test/java/org/springframework/web/server/session/InMemoryWebSessionStoreTests.java

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,16 @@
1515
*/
1616
package org.springframework.web.server.session;
1717

18+
import java.time.Clock;
19+
import java.time.Duration;
20+
1821
import org.junit.Test;
1922

2023
import org.springframework.web.server.WebSession;
2124

25+
import static junit.framework.TestCase.assertSame;
2226
import static org.junit.Assert.assertNotNull;
27+
import static org.junit.Assert.assertNull;
2328
import static org.junit.Assert.assertTrue;
2429

2530
/**
@@ -28,46 +33,62 @@
2833
*/
2934
public class InMemoryWebSessionStoreTests {
3035

31-
private InMemoryWebSessionStore sessionStore = new InMemoryWebSessionStore();
36+
private InMemoryWebSessionStore store = new InMemoryWebSessionStore();
3237

3338

3439
@Test
3540
public void constructorWhenImplicitStartCopiedThenCopyIsStarted() {
36-
WebSession original = this.sessionStore.createWebSession().block();
41+
WebSession original = this.store.createWebSession().block();
3742
assertNotNull(original);
3843
original.getAttributes().put("foo", "bar");
3944

40-
WebSession copy = this.sessionStore.updateLastAccessTime(original).block();
45+
WebSession copy = this.store.updateLastAccessTime(original).block();
4146
assertNotNull(copy);
4247
assertTrue(copy.isStarted());
4348
}
4449

4550
@Test
4651
public void constructorWhenExplicitStartCopiedThenCopyIsStarted() {
47-
WebSession original = this.sessionStore.createWebSession().block();
52+
WebSession original = this.store.createWebSession().block();
4853
assertNotNull(original);
4954
original.start();
5055

51-
WebSession copy = this.sessionStore.updateLastAccessTime(original).block();
56+
WebSession copy = this.store.updateLastAccessTime(original).block();
5257
assertNotNull(copy);
5358
assertTrue(copy.isStarted());
5459
}
5560

5661
@Test
5762
public void startsSessionExplicitly() {
58-
WebSession session = this.sessionStore.createWebSession().block();
63+
WebSession session = this.store.createWebSession().block();
5964
assertNotNull(session);
6065
session.start();
6166
assertTrue(session.isStarted());
6267
}
6368

6469
@Test
6570
public void startsSessionImplicitly() {
66-
WebSession session = this.sessionStore.createWebSession().block();
71+
WebSession session = this.store.createWebSession().block();
6772
assertNotNull(session);
6873
session.start();
6974
session.getAttributes().put("foo", "bar");
7075
assertTrue(session.isStarted());
7176
}
7277

78+
@Test
79+
public void retrieveExpiredSession() throws Exception {
80+
WebSession session = this.store.createWebSession().block();
81+
assertNotNull(session);
82+
session.getAttributes().put("foo", "bar");
83+
session.save();
84+
85+
String id = session.getId();
86+
WebSession retrieved = this.store.retrieveSession(id).block();
87+
assertNotNull(retrieved);
88+
assertSame(session, retrieved);
89+
90+
this.store.setClock(Clock.offset(this.store.getClock(), Duration.ofMinutes(31)));
91+
WebSession retrievedAgain = this.store.retrieveSession(id).block();
92+
assertNull(retrievedAgain);
93+
}
7394
}

spring-web/src/test/java/org/springframework/web/server/session/WebSessionIntegrationTests.java

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import static org.junit.Assert.assertNotEquals;
4444
import static org.junit.Assert.assertNotNull;
4545
import static org.junit.Assert.assertNull;
46+
import static org.junit.Assert.assertTrue;
4647

4748
/**
4849
* Integration tests for with a server-side session.
@@ -109,7 +110,7 @@ public void expiredSessionIsRecreated() throws Exception {
109110
assertNull(response.getHeaders().get("Set-Cookie"));
110111
assertEquals(2, this.handler.getSessionRequestCount());
111112

112-
// Now set the clock of the session back by 31 minutes
113+
// Now fast-forward by 31 minutes
113114
InMemoryWebSessionStore store = (InMemoryWebSessionStore) this.sessionManager.getSessionStore();
114115
WebSession session = store.retrieveSession(id).block();
115116
assertNotNull(session);
@@ -125,6 +126,33 @@ public void expiredSessionIsRecreated() throws Exception {
125126
assertEquals(1, this.handler.getSessionRequestCount());
126127
}
127128

129+
@Test
130+
public void expiredSessionEnds() throws Exception {
131+
132+
// First request: no session yet, new session created
133+
RequestEntity<Void> request = RequestEntity.get(createUri()).build();
134+
ResponseEntity<Void> response = this.restTemplate.exchange(request, Void.class);
135+
136+
assertEquals(HttpStatus.OK, response.getStatusCode());
137+
String id = extractSessionId(response.getHeaders());
138+
assertNotNull(id);
139+
assertEquals(1, this.handler.getSessionRequestCount());
140+
141+
// Now fast-forward by 31 minutes
142+
InMemoryWebSessionStore store = (InMemoryWebSessionStore) this.sessionManager.getSessionStore();
143+
store.setClock(Clock.offset(store.getClock(), Duration.ofMinutes(31)));
144+
145+
// Second request: session expires
146+
URI uri = new URI("http://localhost:" + this.port + "/?expiredSession");
147+
request = RequestEntity.get(uri).header("Cookie", "SESSION=" + id).build();
148+
response = this.restTemplate.exchange(request, Void.class);
149+
150+
assertEquals(HttpStatus.OK, response.getStatusCode());
151+
String value = response.getHeaders().getFirst("Set-Cookie");
152+
assertNotNull(value);
153+
assertTrue("Actual value: " + value, value.contains("Max-Age=0"));
154+
}
155+
128156
@Test
129157
public void changeSessionId() throws Exception {
130158

@@ -178,11 +206,18 @@ public int getSessionRequestCount() {
178206

179207
@Override
180208
public Mono<Void> handle(ServerWebExchange exchange) {
181-
if (exchange.getRequest().getQueryParams().containsKey("changeId")) {
209+
if (exchange.getRequest().getQueryParams().containsKey("expiredSession")) {
210+
return exchange.getSession().doOnNext(session -> {
211+
// Don't do anything, leave it expired...
212+
}).then();
213+
}
214+
else if (exchange.getRequest().getQueryParams().containsKey("changeId")) {
182215
return exchange.getSession().flatMap(session ->
183216
session.changeSessionId().doOnSuccess(aVoid -> updateSessionAttribute(session)));
184217
}
185-
return exchange.getSession().doOnSuccess(this::updateSessionAttribute).then();
218+
else {
219+
return exchange.getSession().doOnSuccess(this::updateSessionAttribute).then();
220+
}
186221
}
187222

188223
private void updateSessionAttribute(WebSession session) {

0 commit comments

Comments
 (0)