Introduce SessionIdGenerationStrategy

Closes gh-11
This commit is contained in:
Marcus Da Coregio
2023-03-07 17:55:18 -03:00
committed by Marcus Hert Da Coregio
parent 2e1275333a
commit d547b33962
39 changed files with 1074 additions and 30 deletions

View File

@@ -74,6 +74,9 @@ public final class MapSession implements Session, Serializable {
*/
private Duration maxInactiveInterval = DEFAULT_MAX_INACTIVE_INTERVAL;
private transient SessionIdGenerationStrategy sessionIdGenerationStrategy = UuidSessionIdGenerationStrategy
.getInstance();
/**
* Creates a new instance with a secure randomly generated identifier.
*/
@@ -81,6 +84,17 @@ public final class MapSession implements Session, Serializable {
this(generateId());
}
/**
* Creates a new instance using the specified {@link SessionIdGenerationStrategy} to
* generate the session id.
* @param sessionIdGenerationStrategy the {@link SessionIdGenerationStrategy} to use.
* @since 3.2
*/
public MapSession(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
this(sessionIdGenerationStrategy.generate());
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
}
/**
* Creates a new instance with the specified id. This is preferred to the default
* constructor when the id is known to prevent unnecessary consumption on entropy
@@ -141,7 +155,7 @@ public final class MapSession implements Session, Serializable {
@Override
public String changeSessionId() {
String changedId = generateId();
String changedId = this.sessionIdGenerationStrategy.generate();
setId(changedId);
return changedId;
}
@@ -232,6 +246,16 @@ public final class MapSession implements Session, Serializable {
return UUID.randomUUID().toString();
}
/**
* Sets the {@link SessionIdGenerationStrategy} to use when generating a new session
* id.
* @param sessionIdGenerationStrategy the {@link SessionIdGenerationStrategy} to use.
* @since 3.2
*/
public void setSessionIdGenerationStrategy(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
}
private static final long serialVersionUID = 7160779239673823561L;
}

View File

@@ -43,6 +43,8 @@ public class MapSessionRepository implements SessionRepository<MapSession> {
private final Map<String, Session> sessions;
private SessionIdGenerationStrategy sessionIdGenerationStrategy = UuidSessionIdGenerationStrategy.getInstance();
/**
* Creates a new instance backed by the provided {@link java.util.Map}. This allows
* injecting a distributed {@link java.util.Map}.
@@ -71,7 +73,9 @@ public class MapSessionRepository implements SessionRepository<MapSession> {
if (!session.getId().equals(session.getOriginalId())) {
this.sessions.remove(session.getOriginalId());
}
this.sessions.put(session.getId(), new MapSession(session));
MapSession saved = new MapSession(session);
saved.setSessionIdGenerationStrategy(this.sessionIdGenerationStrategy);
this.sessions.put(session.getId(), saved);
}
@Override
@@ -84,7 +88,9 @@ public class MapSessionRepository implements SessionRepository<MapSession> {
deleteById(saved.getId());
return null;
}
return new MapSession(saved);
MapSession result = new MapSession(saved);
result.setSessionIdGenerationStrategy(this.sessionIdGenerationStrategy);
return result;
}
@Override
@@ -94,9 +100,14 @@ public class MapSessionRepository implements SessionRepository<MapSession> {
@Override
public MapSession createSession() {
MapSession result = new MapSession();
MapSession result = new MapSession(this.sessionIdGenerationStrategy);
result.setMaxInactiveInterval(this.defaultMaxInactiveInterval);
return result;
}
public void setSessionIdGenerationStrategy(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
Assert.notNull(sessionIdGenerationStrategy, "sessionIdGenerationStrategy cannot be null");
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
}
}

View File

@@ -45,6 +45,8 @@ public class ReactiveMapSessionRepository implements ReactiveSessionRepository<M
private final Map<String, Session> sessions;
private SessionIdGenerationStrategy sessionIdGenerationStrategy = UuidSessionIdGenerationStrategy.getInstance();
/**
* Creates a new instance backed by the provided {@link Map}. This allows injecting a
* distributed {@link Map}.
@@ -84,6 +86,7 @@ public class ReactiveMapSessionRepository implements ReactiveSessionRepository<M
return Mono.defer(() -> Mono.justOrEmpty(this.sessions.get(id))
.filter((session) -> !session.isExpired())
.map(MapSession::new)
.doOnNext((session) -> session.setSessionIdGenerationStrategy(this.sessionIdGenerationStrategy))
.switchIfEmpty(deleteById(id).then(Mono.empty())));
// @formatter:on
}
@@ -96,10 +99,21 @@ public class ReactiveMapSessionRepository implements ReactiveSessionRepository<M
@Override
public Mono<MapSession> createSession() {
return Mono.defer(() -> {
MapSession result = new MapSession();
MapSession result = new MapSession(this.sessionIdGenerationStrategy);
result.setMaxInactiveInterval(this.defaultMaxInactiveInterval);
return Mono.just(result);
});
}
/**
* Sets the {@link SessionIdGenerationStrategy} to use.
* @param sessionIdGenerationStrategy the non-null {@link SessionIdGenerationStrategy}
* to use
* @since 3.2
*/
public void setSessionIdGenerationStrategy(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
Assert.notNull(sessionIdGenerationStrategy, "sessionIdGenerationStrategy cannot be null");
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2014-2023 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.session;
import org.springframework.lang.NonNull;
/**
* An interface for specifying a strategy for generating session identifiers.
*
* @author Marcus da Coregio
* @since 3.2
*/
public interface SessionIdGenerationStrategy {
@NonNull
String generate();
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2014-2023 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.session;
import java.util.UUID;
import org.springframework.lang.NonNull;
/**
* A {@link SessionIdGenerationStrategy} that generates a random UUID to be used as the
* session id.
*
* @author Marcus da Coregio
* @since 3.2
*/
public final class UuidSessionIdGenerationStrategy implements SessionIdGenerationStrategy {
private static final UuidSessionIdGenerationStrategy INSTANCE = new UuidSessionIdGenerationStrategy();
private UuidSessionIdGenerationStrategy() {
}
@Override
@NonNull
public String generate() {
return UUID.randomUUID().toString();
}
/**
* Returns the singleton instance of {@link UuidSessionIdGenerationStrategy}.
* @return the singleton instance of {@link UuidSessionIdGenerationStrategy}
*/
public static UuidSessionIdGenerationStrategy getInstance() {
return INSTANCE;
}
}

View File

@@ -19,6 +19,7 @@ package org.springframework.session;
import java.time.Duration;
import java.time.Instant;
import java.util.Set;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -42,6 +43,19 @@ class MapSessionTests {
.withMessage("session cannot be null");
}
@Test
void constructorWhenSessionIdGenerationStrategyThenUsesStrategy() {
MapSession session = new MapSession(new FixedSessionIdGenerationStrategy("my-id"));
assertThat(session.getId()).isEqualTo("my-id");
}
@Test
void constructorWhenDefaultThenUuid() {
String id = this.session.getId();
UUID uuid = UUID.fromString(id);
assertThat(uuid).isNotNull();
}
@Test
void getAttributeWhenNullThenNull() {
String result = this.session.getAttribute("attrName");
@@ -143,6 +157,41 @@ class MapSessionTests {
assertThat(this.session.getAttributeNames()).isEmpty();
}
@Test
void changeSessionIdWhenSessionIdStrategyThenUsesStrategy() {
MapSession session = new MapSession(new IncrementalSessionIdGenerationStrategy());
String idBeforeChange = session.getId();
String idAfterChange = session.changeSessionId();
assertThat(idBeforeChange).isEqualTo("1");
assertThat(idAfterChange).isEqualTo("2");
}
static class FixedSessionIdGenerationStrategy implements SessionIdGenerationStrategy {
private final String id;
FixedSessionIdGenerationStrategy(String id) {
this.id = id;
}
@Override
public String generate() {
return this.id;
}
}
static class IncrementalSessionIdGenerationStrategy implements SessionIdGenerationStrategy {
private int counter = 1;
@Override
public String generate() {
return String.valueOf(this.counter++);
}
}
static class CustomSession implements Session {
@Override

View File

@@ -152,4 +152,31 @@ class ReactiveMapSessionRepositoryTests {
assertThat(session.getAttributeNames()).isEmpty();
}
@Test
void createSessionWhenSessionIdGenerationStrategyThenUses() {
this.repository.setSessionIdGenerationStrategy(() -> "test");
MapSession session = this.repository.createSession().block();
assertThat(session.getId()).isEqualTo("test");
assertThat(session.changeSessionId()).isEqualTo("test");
}
@Test
void setSessionIdGenerationStrategyWhenNullThenThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setSessionIdGenerationStrategy(null))
.withMessage("sessionIdGenerationStrategy cannot be null");
}
@Test
void findByIdWhenChangeSessionIdThenUsesSessionIdGenerationStrategy() {
this.repository.setSessionIdGenerationStrategy(() -> "test");
MapSession session = this.repository.createSession().block();
this.repository.save(session).block();
MapSession savedSession = this.repository.findById("test").block();
assertThat(savedSession.getId()).isEqualTo("test");
assertThat(savedSession.changeSessionId()).isEqualTo("test");
}
}