Skip to content

Commit c1fbc5e

Browse files
Introduce SessionIdGenerationStrategy
Closes spring-projectsgh-11
1 parent 2e12753 commit c1fbc5e

File tree

39 files changed

+1077
-30
lines changed

39 files changed

+1077
-30
lines changed

spring-session-core/src/main/java/org/springframework/session/MapSession.java

+28-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import java.util.Set;
2626
import java.util.UUID;
2727

28+
import org.springframework.util.Assert;
29+
2830
/**
2931
* <p>
3032
* A {@link Session} implementation that is backed by a {@link java.util.Map}. The
@@ -74,13 +76,27 @@ public final class MapSession implements Session, Serializable {
7476
*/
7577
private Duration maxInactiveInterval = DEFAULT_MAX_INACTIVE_INTERVAL;
7678

79+
private transient SessionIdGenerationStrategy sessionIdGenerationStrategy = UuidSessionIdGenerationStrategy
80+
.getInstance();
81+
7782
/**
7883
* Creates a new instance with a secure randomly generated identifier.
7984
*/
8085
public MapSession() {
8186
this(generateId());
8287
}
8388

89+
/**
90+
* Creates a new instance using the specified {@link SessionIdGenerationStrategy} to
91+
* generate the session id.
92+
* @param sessionIdGenerationStrategy the {@link SessionIdGenerationStrategy} to use.
93+
* @since 3.2
94+
*/
95+
public MapSession(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
96+
this(sessionIdGenerationStrategy.generate());
97+
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
98+
}
99+
84100
/**
85101
* Creates a new instance with the specified id. This is preferred to the default
86102
* constructor when the id is known to prevent unnecessary consumption on entropy
@@ -141,7 +157,7 @@ public String getOriginalId() {
141157

142158
@Override
143159
public String changeSessionId() {
144-
String changedId = generateId();
160+
String changedId = this.sessionIdGenerationStrategy.generate();
145161
setId(changedId);
146162
return changedId;
147163
}
@@ -232,6 +248,17 @@ private static String generateId() {
232248
return UUID.randomUUID().toString();
233249
}
234250

251+
/**
252+
* Sets the {@link SessionIdGenerationStrategy} to use when generating a new session
253+
* id.
254+
* @param sessionIdGenerationStrategy the {@link SessionIdGenerationStrategy} to use.
255+
* @since 3.2
256+
*/
257+
public void setSessionIdGenerationStrategy(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
258+
Assert.notNull(sessionIdGenerationStrategy, "sessionIdGenerationStrategy cannot be null");
259+
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
260+
}
261+
235262
private static final long serialVersionUID = 7160779239673823561L;
236263

237264
}

spring-session-core/src/main/java/org/springframework/session/MapSessionRepository.java

+14-3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ public class MapSessionRepository implements SessionRepository<MapSession> {
4343

4444
private final Map<String, Session> sessions;
4545

46+
private SessionIdGenerationStrategy sessionIdGenerationStrategy = UuidSessionIdGenerationStrategy.getInstance();
47+
4648
/**
4749
* Creates a new instance backed by the provided {@link java.util.Map}. This allows
4850
* injecting a distributed {@link java.util.Map}.
@@ -71,7 +73,9 @@ public void save(MapSession session) {
7173
if (!session.getId().equals(session.getOriginalId())) {
7274
this.sessions.remove(session.getOriginalId());
7375
}
74-
this.sessions.put(session.getId(), new MapSession(session));
76+
MapSession saved = new MapSession(session);
77+
saved.setSessionIdGenerationStrategy(this.sessionIdGenerationStrategy);
78+
this.sessions.put(session.getId(), saved);
7579
}
7680

7781
@Override
@@ -84,7 +88,9 @@ public MapSession findById(String id) {
8488
deleteById(saved.getId());
8589
return null;
8690
}
87-
return new MapSession(saved);
91+
MapSession result = new MapSession(saved);
92+
result.setSessionIdGenerationStrategy(this.sessionIdGenerationStrategy);
93+
return result;
8894
}
8995

9096
@Override
@@ -94,9 +100,14 @@ public void deleteById(String id) {
94100

95101
@Override
96102
public MapSession createSession() {
97-
MapSession result = new MapSession();
103+
MapSession result = new MapSession(this.sessionIdGenerationStrategy);
98104
result.setMaxInactiveInterval(this.defaultMaxInactiveInterval);
99105
return result;
100106
}
101107

108+
public void setSessionIdGenerationStrategy(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
109+
Assert.notNull(sessionIdGenerationStrategy, "sessionIdGenerationStrategy cannot be null");
110+
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
111+
}
112+
102113
}

spring-session-core/src/main/java/org/springframework/session/ReactiveMapSessionRepository.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ public class ReactiveMapSessionRepository implements ReactiveSessionRepository<M
4545

4646
private final Map<String, Session> sessions;
4747

48+
private SessionIdGenerationStrategy sessionIdGenerationStrategy = UuidSessionIdGenerationStrategy.getInstance();
49+
4850
/**
4951
* Creates a new instance backed by the provided {@link Map}. This allows injecting a
5052
* distributed {@link Map}.
@@ -84,6 +86,7 @@ public Mono<MapSession> findById(String id) {
8486
return Mono.defer(() -> Mono.justOrEmpty(this.sessions.get(id))
8587
.filter((session) -> !session.isExpired())
8688
.map(MapSession::new)
89+
.doOnNext((session) -> session.setSessionIdGenerationStrategy(this.sessionIdGenerationStrategy))
8790
.switchIfEmpty(deleteById(id).then(Mono.empty())));
8891
// @formatter:on
8992
}
@@ -96,10 +99,21 @@ public Mono<Void> deleteById(String id) {
9699
@Override
97100
public Mono<MapSession> createSession() {
98101
return Mono.defer(() -> {
99-
MapSession result = new MapSession();
102+
MapSession result = new MapSession(this.sessionIdGenerationStrategy);
100103
result.setMaxInactiveInterval(this.defaultMaxInactiveInterval);
101104
return Mono.just(result);
102105
});
103106
}
104107

108+
/**
109+
* Sets the {@link SessionIdGenerationStrategy} to use.
110+
* @param sessionIdGenerationStrategy the non-null {@link SessionIdGenerationStrategy}
111+
* to use
112+
* @since 3.2
113+
*/
114+
public void setSessionIdGenerationStrategy(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
115+
Assert.notNull(sessionIdGenerationStrategy, "sessionIdGenerationStrategy cannot be null");
116+
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
117+
}
118+
105119
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2014-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.session;
18+
19+
import org.springframework.lang.NonNull;
20+
21+
/**
22+
* An interface for specifying a strategy for generating session identifiers.
23+
*
24+
* @author Marcus da Coregio
25+
* @since 3.2
26+
*/
27+
public interface SessionIdGenerationStrategy {
28+
29+
@NonNull
30+
String generate();
31+
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2014-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.session;
18+
19+
import java.util.UUID;
20+
21+
import org.springframework.lang.NonNull;
22+
23+
/**
24+
* A {@link SessionIdGenerationStrategy} that generates a random UUID to be used as the
25+
* session id.
26+
*
27+
* @author Marcus da Coregio
28+
* @since 3.2
29+
*/
30+
public final class UuidSessionIdGenerationStrategy implements SessionIdGenerationStrategy {
31+
32+
private static final UuidSessionIdGenerationStrategy INSTANCE = new UuidSessionIdGenerationStrategy();
33+
34+
private UuidSessionIdGenerationStrategy() {
35+
}
36+
37+
@Override
38+
@NonNull
39+
public String generate() {
40+
return UUID.randomUUID().toString();
41+
}
42+
43+
/**
44+
* Returns the singleton instance of {@link UuidSessionIdGenerationStrategy}.
45+
* @return the singleton instance of {@link UuidSessionIdGenerationStrategy}
46+
*/
47+
public static UuidSessionIdGenerationStrategy getInstance() {
48+
return INSTANCE;
49+
}
50+
51+
}

spring-session-core/src/test/java/org/springframework/session/MapSessionTests.java

+49
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.time.Duration;
2020
import java.time.Instant;
2121
import java.util.Set;
22+
import java.util.UUID;
2223

2324
import org.junit.jupiter.api.BeforeEach;
2425
import org.junit.jupiter.api.Test;
@@ -42,6 +43,19 @@ void constructorNullSession() {
4243
.withMessage("session cannot be null");
4344
}
4445

46+
@Test
47+
void constructorWhenSessionIdGenerationStrategyThenUsesStrategy() {
48+
MapSession session = new MapSession(new FixedSessionIdGenerationStrategy("my-id"));
49+
assertThat(session.getId()).isEqualTo("my-id");
50+
}
51+
52+
@Test
53+
void constructorWhenDefaultThenUuid() {
54+
String id = this.session.getId();
55+
UUID uuid = UUID.fromString(id);
56+
assertThat(uuid).isNotNull();
57+
}
58+
4559
@Test
4660
void getAttributeWhenNullThenNull() {
4761
String result = this.session.getAttribute("attrName");
@@ -143,6 +157,41 @@ void getAttributeNamesAndRemove() {
143157
assertThat(this.session.getAttributeNames()).isEmpty();
144158
}
145159

160+
@Test
161+
void changeSessionIdWhenSessionIdStrategyThenUsesStrategy() {
162+
MapSession session = new MapSession(new IncrementalSessionIdGenerationStrategy());
163+
String idBeforeChange = session.getId();
164+
String idAfterChange = session.changeSessionId();
165+
assertThat(idBeforeChange).isEqualTo("1");
166+
assertThat(idAfterChange).isEqualTo("2");
167+
}
168+
169+
static class FixedSessionIdGenerationStrategy implements SessionIdGenerationStrategy {
170+
171+
private final String id;
172+
173+
FixedSessionIdGenerationStrategy(String id) {
174+
this.id = id;
175+
}
176+
177+
@Override
178+
public String generate() {
179+
return this.id;
180+
}
181+
182+
}
183+
184+
static class IncrementalSessionIdGenerationStrategy implements SessionIdGenerationStrategy {
185+
186+
private int counter = 1;
187+
188+
@Override
189+
public String generate() {
190+
return String.valueOf(this.counter++);
191+
}
192+
193+
}
194+
146195
static class CustomSession implements Session {
147196

148197
@Override

spring-session-core/src/test/java/org/springframework/session/ReactiveMapSessionRepositoryTests.java

+27
Original file line numberDiff line numberDiff line change
@@ -152,4 +152,31 @@ void getAttributeNamesAndRemove() {
152152
assertThat(session.getAttributeNames()).isEmpty();
153153
}
154154

155+
@Test
156+
void createSessionWhenSessionIdGenerationStrategyThenUses() {
157+
this.repository.setSessionIdGenerationStrategy(() -> "test");
158+
MapSession session = this.repository.createSession().block();
159+
assertThat(session.getId()).isEqualTo("test");
160+
assertThat(session.changeSessionId()).isEqualTo("test");
161+
}
162+
163+
@Test
164+
void setSessionIdGenerationStrategyWhenNullThenThrowsException() {
165+
assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setSessionIdGenerationStrategy(null))
166+
.withMessage("sessionIdGenerationStrategy cannot be null");
167+
}
168+
169+
@Test
170+
void findByIdWhenChangeSessionIdThenUsesSessionIdGenerationStrategy() {
171+
this.repository.setSessionIdGenerationStrategy(() -> "test");
172+
173+
MapSession session = this.repository.createSession().block();
174+
this.repository.save(session).block();
175+
176+
MapSession savedSession = this.repository.findById("test").block();
177+
178+
assertThat(savedSession.getId()).isEqualTo("test");
179+
assertThat(savedSession.changeSessionId()).isEqualTo("test");
180+
}
181+
155182
}

0 commit comments

Comments
 (0)