Skip to content

Commit 6cc4bcd

Browse files
committed
Verify session existence before update in ReactiveRedisOperationsSessionRepository
Currently, ReactiveRedisOperationsSessionRepository#save does not ensure session's existence before executing update. This can result in an invalid session record in Redis, since write use only delta, and in turn to error while retrieving the invalid session record. This commit adds check for session existence if session is being updated. Closes gh-1111
1 parent dc43f5b commit 6cc4bcd

File tree

3 files changed

+66
-26
lines changed

3 files changed

+66
-26
lines changed

spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/ReactiveRedisOperationsSessionRepositoryITests.java

+24
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.session.data.redis;
1818

19+
import java.time.Instant;
20+
1921
import org.junit.Test;
2022
import org.junit.runner.RunWith;
2123

@@ -191,6 +193,28 @@ public void changeSessionIdSaveTwice() {
191193
assertThat(this.repository.findById(originalId).block()).isNull();
192194
}
193195

196+
// gh-1111
197+
@Test
198+
public void changeSessionSaveOldSessionInstance() {
199+
ReactiveRedisOperationsSessionRepository.RedisSession toSave = this.repository
200+
.createSession().block();
201+
String sessionId = toSave.getId();
202+
203+
this.repository.save(toSave).block();
204+
205+
ReactiveRedisOperationsSessionRepository.RedisSession session = this.repository
206+
.findById(sessionId).block();
207+
session.changeSessionId();
208+
session.setLastAccessedTime(Instant.now());
209+
this.repository.save(session).block();
210+
211+
toSave.setLastAccessedTime(Instant.now());
212+
this.repository.save(toSave).block();
213+
214+
assertThat(this.repository.findById(sessionId).block()).isNull();
215+
assertThat(this.repository.findById(session.getId()).block()).isNotNull();
216+
}
217+
194218
@Configuration
195219
@EnableRedisWebSession
196220
static class Config extends BaseConfig {

spring-session-data-redis/src/main/java/org/springframework/session/data/redis/ReactiveRedisOperationsSessionRepository.java

+34-26
Original file line numberDiff line numberDiff line change
@@ -143,24 +143,39 @@ public Mono<RedisSession> createSession() {
143143

144144
@Override
145145
public Mono<Void> save(RedisSession session) {
146-
return session.saveDelta().and((s) -> {
147-
if (session.isNew) {
148-
session.setNew(false);
149-
}
150-
151-
s.onComplete();
152-
});
146+
Mono<Void> result = session.saveChangeSessionId().and(session.saveDelta())
147+
.and((s) -> {
148+
session.isNew = false;
149+
s.onComplete();
150+
});
151+
if (session.isNew) {
152+
return result;
153+
}
154+
else if (session.hasChangedSessionId()) {
155+
String sessionKey = getSessionKey(session.originalSessionId);
156+
return this.sessionRedisOperations.hasKey(sessionKey)
157+
.flatMap((exists) -> exists ? result : Mono.empty());
158+
}
159+
else {
160+
String sessionKey = getSessionKey(session.getId());
161+
return this.sessionRedisOperations.hasKey(sessionKey)
162+
.flatMap((exists) -> exists ? result : Mono.empty());
163+
}
153164
}
154165

155166
@Override
156167
public Mono<RedisSession> findById(String id) {
157168
String sessionKey = getSessionKey(id);
158169

170+
// @formatter:off
159171
return this.sessionRedisOperations.opsForHash().entries(sessionKey)
160172
.collectMap((e) -> e.getKey().toString(), Map.Entry::getValue)
161-
.filter((map) -> !map.isEmpty()).map(new SessionMapper(id))
162-
.filter((session) -> !session.isExpired()).map(RedisSession::new)
173+
.filter((map) -> !map.isEmpty())
174+
.map(new SessionMapper(id))
175+
.filter((session) -> !session.isExpired())
176+
.map(RedisSession::new)
163177
.switchIfEmpty(Mono.defer(() -> deleteById(id).then(Mono.empty())));
178+
// @formatter:on
164179
}
165180

166181
@Override
@@ -285,12 +300,8 @@ public boolean isExpired() {
285300
return this.cached.isExpired();
286301
}
287302

288-
public void setNew(boolean isNew) {
289-
this.isNew = isNew;
290-
}
291-
292-
public boolean isNew() {
293-
return this.isNew;
303+
private boolean hasChangedSessionId() {
304+
return !getId().equals(this.originalSessionId);
294305
}
295306

296307
private void flushImmediateIfNecessary() {
@@ -305,38 +316,35 @@ private void putAndFlush(String a, Object v) {
305316
}
306317

307318
private Mono<Void> saveDelta() {
308-
String sessionId = getId();
309-
Mono<Void> changeSessionId = saveChangeSessionId(sessionId);
310-
311319
if (this.delta.isEmpty()) {
312-
return changeSessionId.and(Mono.empty());
320+
return Mono.empty();
313321
}
314322

315-
String sessionKey = getSessionKey(sessionId);
316-
323+
String sessionKey = getSessionKey(getId());
317324
Mono<Boolean> update = ReactiveRedisOperationsSessionRepository.this.sessionRedisOperations
318325
.opsForHash().putAll(sessionKey, this.delta);
319-
320326
Mono<Boolean> setTtl = ReactiveRedisOperationsSessionRepository.this.sessionRedisOperations
321327
.expire(sessionKey, getMaxInactiveInterval());
322328

323-
return changeSessionId.and(update).and(setTtl).and((s) -> {
329+
return update.and(setTtl).and((s) -> {
324330
this.delta.clear();
325331
s.onComplete();
326332
}).then();
327333
}
328334

329-
private Mono<Void> saveChangeSessionId(String sessionId) {
330-
if (sessionId.equals(this.originalSessionId)) {
335+
private Mono<Void> saveChangeSessionId() {
336+
if (!hasChangedSessionId()) {
331337
return Mono.empty();
332338
}
333339

340+
String sessionId = getId();
341+
334342
Publisher<Void> replaceSessionId = (s) -> {
335343
this.originalSessionId = sessionId;
336344
s.onComplete();
337345
};
338346

339-
if (isNew()) {
347+
if (this.isNew) {
340348
return Mono.from(replaceSessionId);
341349
}
342350
else {

spring-session-data-redis/src/test/java/org/springframework/session/data/redis/ReactiveRedisOperationsSessionRepositoryTests.java

+8
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ public void saveNewSession() {
183183

184184
@Test
185185
public void saveSessionNothingChanged() {
186+
given(this.redisOperations.hasKey(anyString())).willReturn(Mono.just(true));
186187
given(this.redisOperations.expire(anyString(), any()))
187188
.willReturn(Mono.just(true));
188189

@@ -191,12 +192,14 @@ public void saveSessionNothingChanged() {
191192

192193
StepVerifier.create(this.repository.save(session)).verifyComplete();
193194

195+
verify(this.redisOperations).hasKey(anyString());
194196
verifyZeroInteractions(this.redisOperations);
195197
verifyZeroInteractions(this.hashOperations);
196198
}
197199

198200
@Test
199201
public void saveLastAccessChanged() {
202+
given(this.redisOperations.hasKey(anyString())).willReturn(Mono.just(true));
200203
given(this.redisOperations.opsForHash()).willReturn(this.hashOperations);
201204
given(this.hashOperations.putAll(anyString(), any())).willReturn(Mono.just(true));
202205
given(this.redisOperations.expire(anyString(), any()))
@@ -206,6 +209,7 @@ public void saveLastAccessChanged() {
206209
session.setLastAccessedTime(Instant.ofEpochMilli(12345678L));
207210
Mono.just(session).subscribe(this.repository::save);
208211

212+
verify(this.redisOperations).hasKey(anyString());
209213
verify(this.redisOperations).opsForHash();
210214
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
211215
verify(this.redisOperations).expire(anyString(), any());
@@ -219,6 +223,7 @@ public void saveLastAccessChanged() {
219223

220224
@Test
221225
public void saveSetAttribute() {
226+
given(this.redisOperations.hasKey(anyString())).willReturn(Mono.just(true));
222227
given(this.redisOperations.opsForHash()).willReturn(this.hashOperations);
223228
given(this.hashOperations.putAll(anyString(), any())).willReturn(Mono.just(true));
224229
given(this.redisOperations.expire(anyString(), any()))
@@ -229,6 +234,7 @@ public void saveSetAttribute() {
229234
session.setAttribute(attrName, "attrValue");
230235
Mono.just(session).subscribe(this.repository::save);
231236

237+
verify(this.redisOperations).hasKey(anyString());
232238
verify(this.redisOperations).opsForHash();
233239
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
234240
verify(this.redisOperations).expire(anyString(), any());
@@ -242,6 +248,7 @@ public void saveSetAttribute() {
242248

243249
@Test
244250
public void saveRemoveAttribute() {
251+
given(this.redisOperations.hasKey(anyString())).willReturn(Mono.just(true));
245252
given(this.redisOperations.opsForHash()).willReturn(this.hashOperations);
246253
given(this.hashOperations.putAll(anyString(), any())).willReturn(Mono.just(true));
247254
given(this.redisOperations.expire(anyString(), any()))
@@ -252,6 +259,7 @@ public void saveRemoveAttribute() {
252259
session.removeAttribute(attrName);
253260
Mono.just(session).subscribe(this.repository::save);
254261

262+
verify(this.redisOperations).hasKey(anyString());
255263
verify(this.redisOperations).opsForHash();
256264
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
257265
verify(this.redisOperations).expire(anyString(), any());

0 commit comments

Comments
 (0)