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");
}
}

View File

@@ -36,6 +36,8 @@ import org.springframework.data.mongodb.core.index.IndexOperations;
import org.springframework.lang.Nullable;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.MapSession;
import org.springframework.session.SessionIdGenerationStrategy;
import org.springframework.session.UuidSessionIdGenerationStrategy;
import org.springframework.session.events.SessionCreatedEvent;
import org.springframework.session.events.SessionDeletedEvent;
import org.springframework.session.events.SessionExpiredEvent;
@@ -81,6 +83,8 @@ public class MongoIndexedSessionRepository
private ApplicationEventPublisher eventPublisher;
private SessionIdGenerationStrategy sessionIdGenerationStrategy = UuidSessionIdGenerationStrategy.getInstance();
public MongoIndexedSessionRepository(MongoOperations mongoOperations) {
this.mongoOperations = mongoOperations;
}
@@ -88,7 +92,7 @@ public class MongoIndexedSessionRepository
@Override
public MongoSession createSession() {
MongoSession session = new MongoSession();
MongoSession session = new MongoSession(this.sessionIdGenerationStrategy);
session.setMaxInactiveInterval(this.defaultMaxInactiveInterval);
@@ -116,10 +120,13 @@ public class MongoIndexedSessionRepository
MongoSession session = MongoSessionUtils.convertToSession(this.mongoSessionConverter, sessionWrapper);
if (session != null && session.isExpired()) {
publishEvent(new SessionExpiredEvent(this, session));
deleteById(id);
return null;
if (session != null) {
if (session.isExpired()) {
publishEvent(new SessionExpiredEvent(this, session));
deleteById(id);
return null;
}
session.setSessionIdGenerationStrategy(this.sessionIdGenerationStrategy);
}
return session;
@@ -140,6 +147,7 @@ public class MongoIndexedSessionRepository
.map((query) -> this.mongoOperations.find(query, Document.class, this.collectionName))
.orElse(Collections.emptyList()).stream()
.map((dbSession) -> MongoSessionUtils.convertToSession(this.mongoSessionConverter, dbSession))
.peek((session) -> session.setSessionIdGenerationStrategy(this.sessionIdGenerationStrategy))
.collect(Collectors.toMap(MongoSession::getId, (mapSession) -> mapSession));
}
@@ -216,4 +224,14 @@ public class MongoIndexedSessionRepository
this.mongoSessionConverter = mongoSessionConverter;
}
/**
* Set the {@link SessionIdGenerationStrategy} to use to generate session ids.
* @param sessionIdGenerationStrategy the {@link SessionIdGenerationStrategy} to use
* @since 3.2
*/
public void setSessionIdGenerationStrategy(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
Assert.notNull(sessionIdGenerationStrategy, "sessionIdGenerationStrategy cannot be null");
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
}
}

View File

@@ -23,12 +23,14 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.springframework.lang.Nullable;
import org.springframework.session.MapSession;
import org.springframework.session.Session;
import org.springframework.session.SessionIdGenerationStrategy;
import org.springframework.session.UuidSessionIdGenerationStrategy;
import org.springframework.util.Assert;
/**
* Session object providing additional information about the datetime of expiration.
@@ -66,12 +68,24 @@ class MongoSession implements Session {
private Map<String, Object> attrs = new HashMap<>();
private transient SessionIdGenerationStrategy sessionIdGenerationStrategy = UuidSessionIdGenerationStrategy
.getInstance();
/**
* Constructs a new instance using the provided session id.
* @param sessionId the session id to use
* @since 3.2
*/
MongoSession(String sessionId) {
this(sessionId, MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
}
MongoSession() {
this(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
}
MongoSession(long maxInactiveIntervalInSeconds) {
this(UUID.randomUUID().toString(), maxInactiveIntervalInSeconds);
this(UuidSessionIdGenerationStrategy.getInstance().generate(), maxInactiveIntervalInSeconds);
}
MongoSession(String id, long maxInactiveIntervalInSeconds) {
@@ -82,6 +96,28 @@ class MongoSession implements Session {
setLastAccessedTime(Instant.ofEpochMilli(this.createdMillis));
}
/**
* Constructs a new instance using the provided {@link SessionIdGenerationStrategy}.
* @param sessionIdGenerationStrategy the {@link SessionIdGenerationStrategy} to use
* @since 3.2
*/
MongoSession(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
this(sessionIdGenerationStrategy.generate(), MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
}
/**
* Constructs a new instance using the provided {@link SessionIdGenerationStrategy}
* and max inactive interval.
* @param sessionIdGenerationStrategy the {@link SessionIdGenerationStrategy} to use
* @param maxInactiveIntervalInSeconds the max inactive interval in seconds
* @since 3.2
*/
MongoSession(SessionIdGenerationStrategy sessionIdGenerationStrategy, long maxInactiveIntervalInSeconds) {
this(sessionIdGenerationStrategy.generate(), maxInactiveIntervalInSeconds);
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
}
static String coverDot(String attributeName) {
return attributeName.replace('.', DOT_COVER_CHAR);
}
@@ -93,7 +129,7 @@ class MongoSession implements Session {
@Override
public String changeSessionId() {
String changedId = UUID.randomUUID().toString();
String changedId = this.sessionIdGenerationStrategy.generate();
this.id = changedId;
return changedId;
}
@@ -141,7 +177,6 @@ class MongoSession implements Session {
@Override
public void setLastAccessedTime(Instant lastAccessedTime) {
this.accessedMillis = lastAccessedTime.toEpochMilli();
this.expireAt = Date.from(lastAccessedTime.plus(Duration.ofSeconds(this.intervalSeconds)));
}
@@ -200,4 +235,23 @@ class MongoSession implements Session {
return this.originalSessionId;
}
/**
* Sets the session id.
* @param id the id to set
* @since 3.2
*/
void setId(String id) {
this.id = id;
}
/**
* Sets the {@link SessionIdGenerationStrategy} to use.
* @param sessionIdGenerationStrategy the {@link SessionIdGenerationStrategy} to use
* @since 3.2
*/
void setSessionIdGenerationStrategy(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
Assert.notNull(sessionIdGenerationStrategy, "sessionIdGenerationStrategy cannot be null");
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
}
}

View File

@@ -22,6 +22,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bson.Document;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEvent;
@@ -34,6 +35,8 @@ import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.session.MapSession;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.SessionIdGenerationStrategy;
import org.springframework.session.UuidSessionIdGenerationStrategy;
import org.springframework.session.events.SessionCreatedEvent;
import org.springframework.session.events.SessionDeletedEvent;
import org.springframework.util.Assert;
@@ -76,6 +79,8 @@ public class ReactiveMongoSessionRepository
private ApplicationEventPublisher eventPublisher;
private SessionIdGenerationStrategy sessionIdGenerationStrategy = UuidSessionIdGenerationStrategy.getInstance();
public ReactiveMongoSessionRepository(ReactiveMongoOperations mongoOperations) {
this.mongoOperations = mongoOperations;
}
@@ -93,11 +98,17 @@ public class ReactiveMongoSessionRepository
*/
@Override
public Mono<MongoSession> createSession() {
return Mono.justOrEmpty(this.defaultMaxInactiveInterval.toSeconds()) //
.map(MongoSession::new) //
.doOnNext((mongoSession) -> publishEvent(new SessionCreatedEvent(this, mongoSession))) //
.switchIfEmpty(Mono.just(new MongoSession()));
// @formatter:off
return Mono.fromSupplier(() -> this.sessionIdGenerationStrategy.generate())
.map(MongoSession::new)
.doOnNext((mongoSession) -> mongoSession.setMaxInactiveInterval(this.defaultMaxInactiveInterval))
.doOnNext(
(mongoSession) -> mongoSession.setSessionIdGenerationStrategy(this.sessionIdGenerationStrategy))
.doOnNext((mongoSession) -> publishEvent(new SessionCreatedEvent(this, mongoSession)))
.switchIfEmpty(Mono.just(new MongoSession(this.sessionIdGenerationStrategy)))
.subscribeOn(Schedulers.boundedElastic())
.publishOn(Schedulers.parallel());
// @formatter:on
}
@Override
@@ -127,6 +138,8 @@ public class ReactiveMongoSessionRepository
return findSession(id) //
.map((document) -> MongoSessionUtils.convertToSession(this.mongoSessionConverter, document)) //
.filter((mongoSession) -> !mongoSession.isExpired()) //
.doOnNext(
(mongoSession) -> mongoSession.setSessionIdGenerationStrategy(this.sessionIdGenerationStrategy))
.switchIfEmpty(Mono.defer(() -> this.deleteById(id).then(Mono.empty())));
}
@@ -216,4 +229,9 @@ public class ReactiveMongoSessionRepository
this.blockingMongoOperations = blockingMongoOperations;
}
public void setSessionIdGenerationStrategy(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
Assert.notNull(sessionIdGenerationStrategy, "sessionIdGenerationStrategy cannot be null");
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
}
}

View File

@@ -36,6 +36,8 @@ import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.session.IndexResolver;
import org.springframework.session.MapSession;
import org.springframework.session.Session;
import org.springframework.session.SessionIdGenerationStrategy;
import org.springframework.session.UuidSessionIdGenerationStrategy;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
import org.springframework.session.data.mongo.AbstractMongoSessionConverter;
@@ -70,6 +72,8 @@ public class MongoHttpSessionConfiguration implements BeanClassLoaderAware, Embe
private IndexResolver<Session> indexResolver;
private SessionIdGenerationStrategy sessionIdGenerationStrategy = UuidSessionIdGenerationStrategy.getInstance();
@Bean
public MongoIndexedSessionRepository mongoSessionRepository(MongoOperations mongoOperations) {
@@ -98,6 +102,7 @@ public class MongoHttpSessionConfiguration implements BeanClassLoaderAware, Embe
if (StringUtils.hasText(this.collectionName)) {
repository.setCollectionName(this.collectionName);
}
repository.setSessionIdGenerationStrategy(this.sessionIdGenerationStrategy);
this.sessionRepositoryCustomizers
.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(repository));
@@ -160,4 +165,9 @@ public class MongoHttpSessionConfiguration implements BeanClassLoaderAware, Embe
this.indexResolver = indexResolver;
}
@Autowired(required = false)
public void setSessionIdGenerationStrategy(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
}
}

View File

@@ -37,6 +37,8 @@ import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.session.IndexResolver;
import org.springframework.session.MapSession;
import org.springframework.session.Session;
import org.springframework.session.SessionIdGenerationStrategy;
import org.springframework.session.UuidSessionIdGenerationStrategy;
import org.springframework.session.config.ReactiveSessionRepositoryCustomizer;
import org.springframework.session.config.annotation.web.server.SpringWebSessionConfiguration;
import org.springframework.session.data.mongo.AbstractMongoSessionConverter;
@@ -74,6 +76,8 @@ public class ReactiveMongoWebSessionConfiguration
private IndexResolver<Session> indexResolver;
private SessionIdGenerationStrategy sessionIdGenerationStrategy = UuidSessionIdGenerationStrategy.getInstance();
@Bean
public ReactiveMongoSessionRepository reactiveMongoSessionRepository(ReactiveMongoOperations operations) {
@@ -112,6 +116,8 @@ public class ReactiveMongoWebSessionConfiguration
this.sessionRepositoryCustomizers
.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(repository));
repository.setSessionIdGenerationStrategy(this.sessionIdGenerationStrategy);
return repository;
}
@@ -180,4 +186,9 @@ public class ReactiveMongoWebSessionConfiguration
this.indexResolver = indexResolver;
}
@Autowired(required = false)
public void setSessionIdGenerationStrategy(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
}
}

View File

@@ -34,8 +34,10 @@ import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.MapSession;
import org.springframework.session.SessionIdGenerationStrategy;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -209,4 +211,52 @@ public class MongoIndexedSessionRepositoryTest {
assertThat(sessionsMap).isEmpty();
}
@Test
void createSessionWhenSessionIdGenerationStrategyThenUses() {
this.repository.setSessionIdGenerationStrategy(new FixedSessionIdGenerationStrategy("123"));
MongoSession session = this.repository.createSession();
assertThat(session.getId()).isEqualTo("123");
assertThat(session.changeSessionId()).isEqualTo("123");
}
@Test
void setSessionIdGenerationStrategyWhenNullThenThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setSessionIdGenerationStrategy(null));
}
@Test
void findByIdWhenChangeSessionIdThenUsesSessionIdGenerationStrategy() {
this.repository.setSessionIdGenerationStrategy(new FixedSessionIdGenerationStrategy("456"));
Document sessionDocument = new Document();
given(this.mongoOperations.findById("123", Document.class,
MongoIndexedSessionRepository.DEFAULT_COLLECTION_NAME)).willReturn(sessionDocument);
MongoSession session = new MongoSession("123");
given(this.converter.convert(sessionDocument, TypeDescriptor.valueOf(Document.class),
TypeDescriptor.valueOf(MongoSession.class))).willReturn(session);
MongoSession retrievedSession = this.repository.findById("123");
assertThat(retrievedSession.getId()).isEqualTo("123");
String newSessionId = retrievedSession.changeSessionId();
assertThat(newSessionId).isEqualTo("456");
}
static class FixedSessionIdGenerationStrategy implements SessionIdGenerationStrategy {
private final String id;
FixedSessionIdGenerationStrategy(String id) {
this.id = id;
}
@Override
public String generate() {
return this.id;
}
}
}

View File

@@ -40,6 +40,7 @@ import org.springframework.session.MapSession;
import org.springframework.session.events.SessionDeletedEvent;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.BDDMockito.any;
import static org.mockito.BDDMockito.eq;
import static org.mockito.BDDMockito.given;
@@ -210,4 +211,43 @@ public class ReactiveMongoSessionRepositoryTest {
verify(this.converter, times(1)).ensureIndexes(indexOperations);
}
@Test
void createSessionWhenSessionIdGenerationStrategyThenUses() {
this.repository.setSessionIdGenerationStrategy(() -> "test");
this.repository.createSession().as(StepVerifier::create).assertNext((mongoSession) -> {
assertThat(mongoSession.getId()).isEqualTo("test");
assertThat(mongoSession.changeSessionId()).isEqualTo("test");
}).verifyComplete();
}
@Test
void setSessionIdGenerationStrategyWhenNullThenThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setSessionIdGenerationStrategy(null))
.withMessage("sessionIdGenerationStrategy cannot be null");
}
@Test
void findByIdWhenChangeSessionIdThenUsesSessionIdGenerationStrategy() {
this.repository.setSessionIdGenerationStrategy(() -> "test");
String sessionId = UUID.randomUUID().toString();
Document sessionDocument = new Document();
given(this.mongoOperations.findById(sessionId, Document.class,
ReactiveMongoSessionRepository.DEFAULT_COLLECTION_NAME)).willReturn(Mono.just(sessionDocument));
MongoSession session = new MongoSession(sessionId);
given(this.converter.convert(sessionDocument, TypeDescriptor.valueOf(Document.class),
TypeDescriptor.valueOf(MongoSession.class))).willReturn(session);
this.repository.findById(sessionId).as(StepVerifier::create).assertNext((mongoSession) -> {
String oldId = mongoSession.getId();
String newId = mongoSession.changeSessionId();
assertThat(oldId).isEqualTo(sessionId);
assertThat(newId).isEqualTo("test");
}).verifyComplete();
}
}

View File

@@ -34,6 +34,8 @@ import org.springframework.data.mongodb.core.index.IndexOperations;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.session.IndexResolver;
import org.springframework.session.Session;
import org.springframework.session.SessionIdGenerationStrategy;
import org.springframework.session.UuidSessionIdGenerationStrategy;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.data.mongo.AbstractMongoSessionConverter;
import org.springframework.session.data.mongo.JacksonMongoSessionConverter;
@@ -200,6 +202,22 @@ public class MongoHttpSessionConfigurationTest {
assertThat(sessionRepository).extracting("defaultMaxInactiveInterval").isEqualTo(Duration.ZERO);
}
@Test
void registerWhenSessionIdGenerationStrategyBeanThenUses() {
registerAndRefresh(SessionIdGenerationStrategyConfiguration.class);
MongoIndexedSessionRepository sessionRepository = this.context.getBean(MongoIndexedSessionRepository.class);
assertThat(sessionRepository).extracting("sessionIdGenerationStrategy")
.isInstanceOf(TestSessionIdGenerationStrategy.class);
}
@Test
void registerWhenNoSessionIdGenerationStrategyBeanThenDefault() {
registerAndRefresh(DefaultConfiguration.class);
MongoIndexedSessionRepository sessionRepository = this.context.getBean(MongoIndexedSessionRepository.class);
assertThat(sessionRepository).extracting("sessionIdGenerationStrategy")
.isInstanceOf(UuidSessionIdGenerationStrategy.class);
}
private void registerAndRefresh(Class<?>... annotatedClasses) {
this.context.register(annotatedClasses);
@@ -350,4 +368,25 @@ public class MongoHttpSessionConfigurationTest {
}
@Configuration(proxyBeanMethods = false)
@EnableMongoHttpSession
@Import(MongoConfiguration.class)
static class SessionIdGenerationStrategyConfiguration {
@Bean
SessionIdGenerationStrategy sessionIdGenerationStrategy() {
return new TestSessionIdGenerationStrategy();
}
}
static class TestSessionIdGenerationStrategy implements SessionIdGenerationStrategy {
@Override
public String generate() {
return "test";
}
}
}

View File

@@ -35,6 +35,8 @@ import org.springframework.data.mongodb.core.index.IndexOperations;
import org.springframework.session.IndexResolver;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.SessionIdGenerationStrategy;
import org.springframework.session.UuidSessionIdGenerationStrategy;
import org.springframework.session.config.ReactiveSessionRepositoryCustomizer;
import org.springframework.session.config.annotation.web.server.EnableSpringWebSession;
import org.springframework.session.data.mongo.AbstractMongoSessionConverter;
@@ -222,6 +224,28 @@ public class ReactiveMongoWebSessionConfigurationTest {
assertThat(sessionRepository).extracting("defaultMaxInactiveInterval").isEqualTo(Duration.ZERO);
}
@Test
void registerWhenSessionIdGenerationStrategyBeanThenUses() {
registerAndRefresh(GoodConfig.class, SessionIdGenerationStrategyConfiguration.class);
ReactiveMongoSessionRepository sessionRepository = this.context.getBean(ReactiveMongoSessionRepository.class);
assertThat(sessionRepository).extracting("sessionIdGenerationStrategy")
.isInstanceOf(TestSessionIdGenerationStrategy.class);
}
@Test
void registerWhenNoSessionIdGenerationStrategyBeanThenDefault() {
registerAndRefresh(GoodConfig.class);
ReactiveMongoSessionRepository sessionRepository = this.context.getBean(ReactiveMongoSessionRepository.class);
assertThat(sessionRepository).extracting("sessionIdGenerationStrategy")
.isInstanceOf(UuidSessionIdGenerationStrategy.class);
}
private void registerAndRefresh(Class<?>... annotatedClasses) {
this.context = new AnnotationConfigApplicationContext();
this.context.register(annotatedClasses);
this.context.refresh();
}
/**
* Reflectively extract the {@link AbstractMongoSessionConverter} from the
* {@link ReactiveMongoSessionRepository}. This is to avoid expanding the surface area
@@ -411,4 +435,23 @@ public class ReactiveMongoWebSessionConfigurationTest {
}
@Configuration(proxyBeanMethods = false)
static class SessionIdGenerationStrategyConfiguration {
@Bean
SessionIdGenerationStrategy sessionIdGenerationStrategy() {
return new TestSessionIdGenerationStrategy();
}
}
static class TestSessionIdGenerationStrategy implements SessionIdGenerationStrategy {
@Override
public String generate() {
return "test";
}
}
}

View File

@@ -31,6 +31,8 @@ import org.springframework.session.MapSession;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.SaveMode;
import org.springframework.session.Session;
import org.springframework.session.SessionIdGenerationStrategy;
import org.springframework.session.UuidSessionIdGenerationStrategy;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -61,6 +63,8 @@ public class ReactiveRedisSessionRepository
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
private SessionIdGenerationStrategy sessionIdGenerationStrategy = UuidSessionIdGenerationStrategy.getInstance();
/**
* Create a new {@link ReactiveRedisSessionRepository} instance.
* @param sessionRedisOperations the {@link ReactiveRedisOperations} to use for
@@ -120,7 +124,7 @@ public class ReactiveRedisSessionRepository
@Override
public Mono<RedisSession> createSession() {
return Mono.defer(() -> {
MapSession cached = new MapSession();
MapSession cached = new MapSession(this.sessionIdGenerationStrategy);
cached.setMaxInactiveInterval(this.defaultMaxInactiveInterval);
RedisSession session = new RedisSession(cached, true);
return Mono.just(session);
@@ -167,6 +171,16 @@ public class ReactiveRedisSessionRepository
return this.namespace + "sessions:" + sessionId;
}
/**
* Set the {@link SessionIdGenerationStrategy} to use to generate session ids.
* @param sessionIdGenerationStrategy the {@link SessionIdGenerationStrategy} to use
* @since 3.2
*/
public void setSessionIdGenerationStrategy(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
Assert.notNull(sessionIdGenerationStrategy, "sessionIdGenerationStrategy cannot be null");
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
}
/**
* A custom implementation of {@link Session} that uses a {@link MapSession} as the
* basis for its mapping. It keeps track of any attributes that have changed. When
@@ -206,7 +220,9 @@ public class ReactiveRedisSessionRepository
@Override
public String changeSessionId() {
return this.cached.changeSessionId();
String newSessionId = ReactiveRedisSessionRepository.this.sessionIdGenerationStrategy.generate();
this.cached.setId(newSessionId);
return newSessionId;
}
@Override

View File

@@ -52,6 +52,8 @@ import org.springframework.session.MapSession;
import org.springframework.session.PrincipalNameIndexResolver;
import org.springframework.session.SaveMode;
import org.springframework.session.Session;
import org.springframework.session.SessionIdGenerationStrategy;
import org.springframework.session.UuidSessionIdGenerationStrategy;
import org.springframework.session.events.SessionCreatedEvent;
import org.springframework.session.events.SessionDeletedEvent;
import org.springframework.session.events.SessionDestroyedEvent;
@@ -322,6 +324,8 @@ public class RedisIndexedSessionRepository
private ThreadPoolTaskScheduler taskScheduler;
private SessionIdGenerationStrategy sessionIdGenerationStrategy = UuidSessionIdGenerationStrategy.getInstance();
/**
* Creates a new instance. For an example, refer to the class level javadoc.
* @param sessionRedisOperations the {@link RedisOperations} to use for managing the
@@ -547,7 +551,7 @@ public class RedisIndexedSessionRepository
@Override
public RedisSession createSession() {
MapSession cached = new MapSession();
MapSession cached = new MapSession(this.sessionIdGenerationStrategy);
cached.setMaxInactiveInterval(this.defaultMaxInactiveInterval);
RedisSession session = new RedisSession(cached, true);
session.flushImmediateIfNecessary();
@@ -716,6 +720,16 @@ public class RedisIndexedSessionRepository
return RedisSessionMapper.ATTRIBUTE_PREFIX + attributeName;
}
/**
* Set the {@link SessionIdGenerationStrategy} to use to generate session ids.
* @param sessionIdGenerationStrategy the {@link SessionIdGenerationStrategy} to use
* @since 3.2
*/
public void setSessionIdGenerationStrategy(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
Assert.notNull(sessionIdGenerationStrategy, "sessionIdGenerationStrategy cannot be null");
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
}
/**
* A custom implementation of {@link Session} that uses a {@link MapSession} as the
* basis for its mapping. It keeps track of any attributes that have changed. When
@@ -780,7 +794,9 @@ public class RedisIndexedSessionRepository
@Override
public String changeSessionId() {
return this.cached.changeSessionId();
String newSessionId = RedisIndexedSessionRepository.this.sessionIdGenerationStrategy.generate();
this.cached.setId(newSessionId);
return newSessionId;
}
@Override

View File

@@ -27,7 +27,9 @@ import org.springframework.session.FlushMode;
import org.springframework.session.MapSession;
import org.springframework.session.SaveMode;
import org.springframework.session.Session;
import org.springframework.session.SessionIdGenerationStrategy;
import org.springframework.session.SessionRepository;
import org.springframework.session.UuidSessionIdGenerationStrategy;
import org.springframework.util.Assert;
/**
@@ -56,6 +58,8 @@ public class RedisSessionRepository implements SessionRepository<RedisSessionRep
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
private SessionIdGenerationStrategy sessionIdGenerationStrategy = UuidSessionIdGenerationStrategy.getInstance();
/**
* Create a new {@link RedisSessionRepository} instance.
* @param sessionRedisOperations the {@link RedisOperations} to use for managing
@@ -106,7 +110,7 @@ public class RedisSessionRepository implements SessionRepository<RedisSessionRep
@Override
public RedisSession createSession() {
MapSession cached = new MapSession();
MapSession cached = new MapSession(this.sessionIdGenerationStrategy);
cached.setMaxInactiveInterval(this.defaultMaxInactiveInterval);
RedisSession session = new RedisSession(cached, true);
session.flushIfRequired();
@@ -162,6 +166,16 @@ public class RedisSessionRepository implements SessionRepository<RedisSessionRep
return RedisSessionMapper.ATTRIBUTE_PREFIX + attributeName;
}
/**
* Set the {@link SessionIdGenerationStrategy} to use to generate session ids.
* @param sessionIdGenerationStrategy the {@link SessionIdGenerationStrategy} to use
* @since 3.2
*/
public void setSessionIdGenerationStrategy(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
Assert.notNull(sessionIdGenerationStrategy, "sessionIdGenerationStrategy cannot be null");
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
}
/**
* An internal {@link Session} implementation used by this {@link SessionRepository}.
*/
@@ -198,7 +212,9 @@ public class RedisSessionRepository implements SessionRepository<RedisSessionRep
@Override
public String changeSessionId() {
return this.cached.changeSessionId();
String newSessionId = RedisSessionRepository.this.sessionIdGenerationStrategy.generate();
this.cached.setId(newSessionId);
return newSessionId;
}
@Override

View File

@@ -19,6 +19,7 @@ package org.springframework.session.data.redis.config.annotation.web.http;
import java.time.Duration;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -27,6 +28,8 @@ import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.session.SessionIdGenerationStrategy;
import org.springframework.session.UuidSessionIdGenerationStrategy;
import org.springframework.session.data.redis.RedisSessionRepository;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.util.StringUtils;
@@ -49,6 +52,8 @@ public class RedisHttpSessionConfiguration extends AbstractRedisHttpSessionConfi
private StringValueResolver embeddedValueResolver;
private SessionIdGenerationStrategy sessionIdGenerationStrategy = UuidSessionIdGenerationStrategy.getInstance();
@Bean
@Override
public RedisSessionRepository sessionRepository() {
@@ -60,6 +65,7 @@ public class RedisHttpSessionConfiguration extends AbstractRedisHttpSessionConfi
}
sessionRepository.setFlushMode(getFlushMode());
sessionRepository.setSaveMode(getSaveMode());
sessionRepository.setSessionIdGenerationStrategy(this.sessionIdGenerationStrategy);
getSessionRepositoryCustomizers()
.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository));
return sessionRepository;
@@ -87,4 +93,9 @@ public class RedisHttpSessionConfiguration extends AbstractRedisHttpSessionConfi
setSaveMode(attributes.getEnum("saveMode"));
}
@Autowired(required = false)
public void setSessionIdGenerationStrategy(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
}
}

View File

@@ -44,6 +44,8 @@ import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.session.IndexResolver;
import org.springframework.session.Session;
import org.springframework.session.SessionIdGenerationStrategy;
import org.springframework.session.UuidSessionIdGenerationStrategy;
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction;
import org.springframework.session.data.redis.config.ConfigureRedisAction;
@@ -80,6 +82,8 @@ public class RedisIndexedHttpSessionConfiguration
private StringValueResolver embeddedValueResolver;
private SessionIdGenerationStrategy sessionIdGenerationStrategy = UuidSessionIdGenerationStrategy.getInstance();
@Bean
@Override
public RedisIndexedSessionRepository sessionRepository() {
@@ -101,6 +105,7 @@ public class RedisIndexedHttpSessionConfiguration
sessionRepository.setCleanupCron(this.cleanupCron);
int database = resolveDatabase();
sessionRepository.setDatabase(database);
sessionRepository.setSessionIdGenerationStrategy(this.sessionIdGenerationStrategy);
getSessionRepositoryCustomizers()
.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository));
return sessionRepository;
@@ -204,6 +209,11 @@ public class RedisIndexedHttpSessionConfiguration
return RedisIndexedSessionRepository.DEFAULT_DATABASE;
}
@Autowired(required = false)
public void setSessionIdGenerationStrategy(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
}
/**
* Ensures that Redis is configured to send keyspace notifications. This is important
* to ensure that expiration and deletion of sessions trigger SessionDestroyedEvents.

View File

@@ -39,6 +39,8 @@ import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.session.MapSession;
import org.springframework.session.SaveMode;
import org.springframework.session.SessionIdGenerationStrategy;
import org.springframework.session.UuidSessionIdGenerationStrategy;
import org.springframework.session.config.ReactiveSessionRepositoryCustomizer;
import org.springframework.session.config.annotation.web.server.SpringWebSessionConfiguration;
import org.springframework.session.data.redis.ReactiveRedisSessionRepository;
@@ -77,6 +79,8 @@ public class RedisWebSessionConfiguration implements BeanClassLoaderAware, Embed
private StringValueResolver embeddedValueResolver;
private SessionIdGenerationStrategy sessionIdGenerationStrategy = UuidSessionIdGenerationStrategy.getInstance();
@Bean
public ReactiveRedisSessionRepository sessionRepository() {
ReactiveRedisTemplate<String, Object> reactiveRedisTemplate = createReactiveRedisTemplate();
@@ -86,6 +90,7 @@ public class RedisWebSessionConfiguration implements BeanClassLoaderAware, Embed
sessionRepository.setRedisKeyNamespace(this.redisNamespace);
}
sessionRepository.setSaveMode(this.saveMode);
sessionRepository.setSessionIdGenerationStrategy(this.sessionIdGenerationStrategy);
this.sessionRepositoryCustomizers
.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository));
return sessionRepository;
@@ -168,4 +173,9 @@ public class RedisWebSessionConfiguration implements BeanClassLoaderAware, Embed
return new ReactiveRedisTemplate<>(this.redisConnectionFactory, serializationContext);
}
@Autowired(required = false)
public void setSessionIdGenerationStrategy(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
}
}

View File

@@ -438,6 +438,46 @@ class ReactiveRedisSessionRepositoryTests {
verifyNoMoreInteractions(this.hashOperations);
}
@Test
void createSessionWhenSessionIdGenerationStrategyThenUses() {
this.repository.setSessionIdGenerationStrategy(() -> "test");
this.repository.createSession().as(StepVerifier::create).assertNext((redisSession) -> {
assertThat(redisSession.getId()).isEqualTo("test");
assertThat(redisSession.changeSessionId()).isEqualTo("test");
}).verifyComplete();
}
@Test
void setSessionIdGenerationStrategyWhenNullThenThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setSessionIdGenerationStrategy(null))
.withMessage("sessionIdGenerationStrategy cannot be null");
}
@Test
@SuppressWarnings("unchecked")
void findByIdWhenChangeSessionIdThenUsesSessionIdGenerationStrategy() {
this.repository.setSessionIdGenerationStrategy(() -> "changed-session-id");
given(this.redisOperations.opsForHash()).willReturn(this.hashOperations);
String attribute1 = "attribute1";
String attribute2 = "attribute2";
MapSession expected = new MapSession("test");
expected.setLastAccessedTime(Instant.now().minusSeconds(60));
expected.setAttribute(attribute1, "test");
expected.setAttribute(attribute2, null);
Map map = map(RedisSessionMapper.ATTRIBUTE_PREFIX + attribute1, expected.getAttribute(attribute1),
RedisSessionMapper.ATTRIBUTE_PREFIX + attribute2, expected.getAttribute(attribute2),
RedisSessionMapper.CREATION_TIME_KEY, expected.getCreationTime().toEpochMilli(),
RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, (int) expected.getMaxInactiveInterval().getSeconds(),
RedisSessionMapper.LAST_ACCESSED_TIME_KEY, expected.getLastAccessedTime().toEpochMilli());
given(this.hashOperations.entries(anyString())).willReturn(Flux.fromIterable(map.entrySet()));
StepVerifier.create(this.repository.findById("test")).consumeNextWith((session) -> {
assertThat(session.getId()).isEqualTo(expected.getId());
assertThat(session.changeSessionId()).isEqualTo("changed-session-id");
}).verifyComplete();
}
private Map<String, Object> map(Object... objects) {
Map<String, Object> result = new HashMap<>();
if (objects == null) {

View File

@@ -910,6 +910,46 @@ class RedisIndexedSessionRepositoryTests {
assertThat(getDelta()).hasSize(3);
}
@Test
void createSessionWhenSessionIdGenerationStrategyThenUses() {
this.redisRepository.setSessionIdGenerationStrategy(() -> "test");
RedisSession session = this.redisRepository.createSession();
assertThat(session.getId()).isEqualTo("test");
assertThat(session.changeSessionId()).isEqualTo("test");
}
@Test
void setSessionIdGenerationStrategyWhenNullThenThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.redisRepository.setSessionIdGenerationStrategy(null))
.withMessage("sessionIdGenerationStrategy cannot be null");
}
@Test
void findByIdWhenChangeSessionIdThenUsesSessionIdGenerationStrategy() {
this.redisRepository.setSessionIdGenerationStrategy(() -> "test");
String attribute1 = "attribute1";
String attribute2 = "attribute2";
MapSession expected = new MapSession("original");
expected.setLastAccessedTime(Instant.now().minusSeconds(60));
expected.setAttribute(attribute1, "test");
expected.setAttribute(attribute2, null);
given(this.redisOperations.<String, Object>boundHashOps(getKey(expected.getId())))
.willReturn(this.boundHashOperations);
Map<String, Object> map = map(RedisIndexedSessionRepository.getSessionAttrNameKey(attribute1),
expected.getAttribute(attribute1), RedisIndexedSessionRepository.getSessionAttrNameKey(attribute2),
expected.getAttribute(attribute2), RedisSessionMapper.CREATION_TIME_KEY,
expected.getCreationTime().toEpochMilli(), RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY,
(int) expected.getMaxInactiveInterval().getSeconds(), RedisSessionMapper.LAST_ACCESSED_TIME_KEY,
expected.getLastAccessedTime().toEpochMilli());
given(this.boundHashOperations.entries()).willReturn(map);
RedisSession session = this.redisRepository.findById(expected.getId());
String oldSessionId = session.getId();
String newSessionId = session.changeSessionId();
assertThat(oldSessionId).isEqualTo("original");
assertThat(newSessionId).isEqualTo("test");
}
private String getKey(String id) {
return "spring:session:sessions:" + id;
}

View File

@@ -373,6 +373,35 @@ class RedisSessionRepositoryTests {
verifyNoMoreInteractions(this.sessionHashOperations);
}
@Test
void createSessionWhenSessionIdGenerationStrategyThenUses() {
this.sessionRepository.setSessionIdGenerationStrategy(() -> "test");
RedisSessionRepository.RedisSession session = this.sessionRepository.createSession();
assertThat(session.getId()).isEqualTo("test");
assertThat(session.changeSessionId()).isEqualTo("test");
}
@Test
void setSessionIdGenerationStrategyWhenNullThenThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.sessionRepository.setSessionIdGenerationStrategy(null))
.withMessage("sessionIdGenerationStrategy cannot be null");
}
@Test
void findByIdWhenChangeSessionIdThenUsesSessionIdGenerationStrategy() {
this.sessionRepository.setSessionIdGenerationStrategy(() -> "test");
Instant now = Instant.now().truncatedTo(ChronoUnit.MILLIS);
given(this.sessionHashOperations.entries(eq(TEST_SESSION_KEY)))
.willReturn(mapOf(RedisSessionMapper.CREATION_TIME_KEY, Instant.EPOCH.toEpochMilli(),
RedisSessionMapper.LAST_ACCESSED_TIME_KEY, now.toEpochMilli(),
RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS,
RedisSessionMapper.ATTRIBUTE_PREFIX + "attribute1", "value1"));
RedisSession session = this.sessionRepository.findById(TEST_SESSION_ID);
assertThat(session.getId()).isEqualTo(TEST_SESSION_ID);
assertThat(session.changeSessionId()).isEqualTo("test");
}
private static String getSessionKey(String sessionId) {
return "spring:session:sessions:" + sessionId;
}

View File

@@ -39,6 +39,8 @@ import org.springframework.data.redis.core.RedisOperations;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.session.FlushMode;
import org.springframework.session.SaveMode;
import org.springframework.session.SessionIdGenerationStrategy;
import org.springframework.session.UuidSessionIdGenerationStrategy;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.data.redis.RedisSessionRepository;
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory;
@@ -206,6 +208,22 @@ class RedisHttpsSessionConfigurationTests {
assertThat(sessionRepository).extracting("defaultMaxInactiveInterval").isEqualTo(Duration.ZERO);
}
@Test
void registerWhenSessionIdGenerationStrategyBeanThenUses() {
registerAndRefresh(RedisConfig.class, SessionIdGenerationStrategyConfiguration.class);
RedisSessionRepository sessionRepository = this.context.getBean(RedisSessionRepository.class);
assertThat(sessionRepository).extracting("sessionIdGenerationStrategy")
.isInstanceOf(TestSessionIdGenerationStrategy.class);
}
@Test
void registerWhenNoSessionIdGenerationStrategyBeanThenDefault() {
registerAndRefresh(RedisConfig.class, DefaultConfiguration.class);
RedisSessionRepository sessionRepository = this.context.getBean(RedisSessionRepository.class);
assertThat(sessionRepository).extracting("sessionIdGenerationStrategy")
.isInstanceOf(UuidSessionIdGenerationStrategy.class);
}
private void registerAndRefresh(Class<?>... annotatedClasses) {
this.context.register(annotatedClasses);
this.context.refresh();
@@ -381,4 +399,30 @@ class RedisHttpsSessionConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
@EnableRedisHttpSession
static class SessionIdGenerationStrategyConfiguration {
@Bean
SessionIdGenerationStrategy sessionIdGenerationStrategy() {
return new TestSessionIdGenerationStrategy();
}
}
@Configuration(proxyBeanMethods = false)
@EnableRedisHttpSession
static class DefaultConfiguration {
}
static class TestSessionIdGenerationStrategy implements SessionIdGenerationStrategy {
@Override
public String generate() {
return "test";
}
}
}

View File

@@ -43,6 +43,8 @@ import org.springframework.session.FlushMode;
import org.springframework.session.IndexResolver;
import org.springframework.session.SaveMode;
import org.springframework.session.Session;
import org.springframework.session.SessionIdGenerationStrategy;
import org.springframework.session.UuidSessionIdGenerationStrategy;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory;
@@ -240,6 +242,22 @@ class RedisIndexedHttpSessionConfigurationTests {
assertThat(sessionRepository).extracting("defaultMaxInactiveInterval").isEqualTo(Duration.ZERO);
}
@Test
void registerWhenSessionIdGenerationStrategyBeanThenUses() {
registerAndRefresh(RedisConfig.class, SessionIdGenerationStrategyConfiguration.class);
RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class);
assertThat(sessionRepository).extracting("sessionIdGenerationStrategy")
.isInstanceOf(TestSessionIdGenerationStrategy.class);
}
@Test
void registerWhenNoSessionIdGenerationStrategyBeanThenDefault() {
registerAndRefresh(RedisConfig.class, DefaultConfiguration.class);
RedisIndexedSessionRepository sessionRepository = this.context.getBean(RedisIndexedSessionRepository.class);
assertThat(sessionRepository).extracting("sessionIdGenerationStrategy")
.isInstanceOf(UuidSessionIdGenerationStrategy.class);
}
private void registerAndRefresh(Class<?>... annotatedClasses) {
this.context.register(annotatedClasses);
this.context.refresh();
@@ -444,4 +462,30 @@ class RedisIndexedHttpSessionConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
@EnableRedisIndexedHttpSession
static class SessionIdGenerationStrategyConfiguration {
@Bean
SessionIdGenerationStrategy sessionIdGenerationStrategy() {
return new TestSessionIdGenerationStrategy();
}
}
@Configuration(proxyBeanMethods = false)
@EnableRedisIndexedHttpSession
static class DefaultConfiguration {
}
static class TestSessionIdGenerationStrategy implements SessionIdGenerationStrategy {
@Override
public String generate() {
return "test";
}
}
}

View File

@@ -17,6 +17,7 @@
** XML Configuration
* xref:configurations.adoc[Configurations]
** xref:configuration/redis.adoc[Redis]
** xref:configuration/common.adoc[Common Configurations]
* xref:http-session.adoc[HttpSession Integration]
* xref:web-socket.adoc[WebSocket Integration]
* xref:web-session.adoc[WebSession Integration]

View File

@@ -0,0 +1,57 @@
[[common-configurations]]
= Common Configurations
This section contains common configurations that applies to all or most Spring Session modules.
It contains configuration examples for the following use cases:
- I need to <<changing-how-session-ids-are-generated,change the way that Session IDs are generated>>
[[changing-how-session-ids-are-generated]]
== Changing How Session IDs Are Generated
By default, Spring Session uses `UuidSessionIdGenerationStrategy` which, in turn, uses a `java.util.UUID` to generate a session id.
There might be scenarios where it may be better to include other characters to increase entropy, or you may want to use a different algorithm to generate the session id.
To change this, you can provide a custom `SessionIdGenerationStrategy` bean:
.Changing How Session IDs Are Generated
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
public SessionIdGenerationStrategy sessionIdGenerationStrategy() {
return new MySessionIdGenerationStrategy();
}
class MySessionIdGenerationStrategy implements SessionIdGenerationStrategy {
@Override
public String generate() {
// ...
}
}
----
======
After exposing your `SessionIdGenerationStrategy` bean, Spring Session will use it to generate session ids.
If you are manually configuring your `SessionRepository` bean (instead of using `@EnableRedisHttpSession`, for example), you can set the `SessionIdGenerationStrategy` directly on the `SessionRepository` implementation:
.Setting `SessionIdGenerationStrategy` directly into `SessionRepository` implementation
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
public RedisSessionRepository redisSessionRepository(RedisOperations redisOperations) {
RedisSessionRepository repository = new RedisSessionRepository(redisOperations)
repository.setSessionIdGenerationStrategy(new MySessionIdGenerationStrategy());
return repository;
}
----
======

View File

@@ -1 +1,3 @@
= What's New
- xref:configuration/common.adoc#changing-how-session-ids-are-generated[docs] - https://github.com/spring-projects/spring-session/issues/11[gh-11] - Introduce `SessionIdGenerationStrategy` to allow custom session id generation

View File

@@ -48,6 +48,8 @@ import org.springframework.session.MapSession;
import org.springframework.session.PrincipalNameIndexResolver;
import org.springframework.session.SaveMode;
import org.springframework.session.Session;
import org.springframework.session.SessionIdGenerationStrategy;
import org.springframework.session.UuidSessionIdGenerationStrategy;
import org.springframework.session.events.AbstractSessionEvent;
import org.springframework.session.events.SessionCreatedEvent;
import org.springframework.session.events.SessionDeletedEvent;
@@ -151,6 +153,8 @@ public class HazelcastIndexedSessionRepository
private UUID sessionListenerId;
private SessionIdGenerationStrategy sessionIdGenerationStrategy = UuidSessionIdGenerationStrategy.getInstance();
/**
* Create a new {@link HazelcastIndexedSessionRepository} instance.
* @param hazelcastInstance the {@link HazelcastInstance} to use for managing sessions
@@ -245,7 +249,7 @@ public class HazelcastIndexedSessionRepository
@Override
public HazelcastSession createSession() {
MapSession cached = new MapSession();
MapSession cached = new MapSession(this.sessionIdGenerationStrategy);
cached.setMaxInactiveInterval(this.defaultMaxInactiveInterval);
HazelcastSession session = new HazelcastSession(cached, true);
session.flushImmediateIfNecessary();
@@ -349,6 +353,16 @@ public class HazelcastIndexedSessionRepository
this.eventPublisher.publishEvent(new SessionExpiredEvent(this, event.getOldValue()));
}
/**
* Set the {@link SessionIdGenerationStrategy} to use to generate session ids.
* @param sessionIdGenerationStrategy the {@link SessionIdGenerationStrategy} to use
* @since 3.2
*/
public void setSessionIdGenerationStrategy(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
Assert.notNull(sessionIdGenerationStrategy, "sessionIdGenerationStrategy cannot be null");
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
}
/**
* A custom implementation of {@link Session} that uses a {@link MapSession} as the
* basis for its mapping. It keeps track if changes have been made since last save.
@@ -405,7 +419,8 @@ public class HazelcastIndexedSessionRepository
@Override
public String changeSessionId() {
String newSessionId = this.delegate.changeSessionId();
String newSessionId = HazelcastIndexedSessionRepository.this.sessionIdGenerationStrategy.generate();
this.delegate.setId(newSessionId);
this.sessionIdChanged = true;
return newSessionId;
}

View File

@@ -38,6 +38,8 @@ import org.springframework.session.IndexResolver;
import org.springframework.session.MapSession;
import org.springframework.session.SaveMode;
import org.springframework.session.Session;
import org.springframework.session.SessionIdGenerationStrategy;
import org.springframework.session.UuidSessionIdGenerationStrategy;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository;
@@ -75,6 +77,8 @@ public class HazelcastHttpSessionConfiguration implements ImportAware {
private List<SessionRepositoryCustomizer<HazelcastIndexedSessionRepository>> sessionRepositoryCustomizers;
private SessionIdGenerationStrategy sessionIdGenerationStrategy = UuidSessionIdGenerationStrategy.getInstance();
@Bean
public FindByIndexNameSessionRepository<?> sessionRepository() {
return createHazelcastIndexedSessionRepository();
@@ -158,9 +162,15 @@ public class HazelcastHttpSessionConfiguration implements ImportAware {
sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveInterval);
sessionRepository.setFlushMode(this.flushMode);
sessionRepository.setSaveMode(this.saveMode);
sessionRepository.setSessionIdGenerationStrategy(this.sessionIdGenerationStrategy);
this.sessionRepositoryCustomizers
.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository));
return sessionRepository;
}
@Autowired(required = false)
public void setSessionIdGenerationStrategy(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
}
}

View File

@@ -465,4 +465,31 @@ class HazelcastIndexedSessionRepositoryTests {
verifyNoMoreInteractions(this.sessions);
}
@Test
void createSessionWhenSessionIdGenerationStrategyThenUses() {
this.repository.setSessionIdGenerationStrategy(() -> "test");
HazelcastSession session = this.repository.createSession();
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 saved = new MapSession("original");
saved.setAttribute("savedName", "savedValue");
given(this.sessions.get(eq(saved.getId()))).willReturn(saved);
HazelcastSession session = this.repository.findById(saved.getId());
assertThat(session.getId()).isEqualTo(saved.getId());
assertThat(session.changeSessionId()).isEqualTo("test");
}
}

View File

@@ -36,6 +36,8 @@ import org.springframework.session.FlushMode;
import org.springframework.session.IndexResolver;
import org.springframework.session.SaveMode;
import org.springframework.session.Session;
import org.springframework.session.SessionIdGenerationStrategy;
import org.springframework.session.UuidSessionIdGenerationStrategy;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository;
import org.springframework.session.hazelcast.config.annotation.SpringSessionHazelcastInstance;
@@ -238,6 +240,24 @@ class HazelcastHttpSessionConfigurationTests {
assertThat(sessionRepository).extracting("defaultMaxInactiveInterval").isEqualTo(Duration.ZERO);
}
@Test
void registerWhenSessionIdGenerationStrategyBeanThenUses() {
registerAndRefresh(DefaultConfiguration.class, SessionIdGenerationStrategyConfiguration.class);
HazelcastIndexedSessionRepository sessionRepository = this.context
.getBean(HazelcastIndexedSessionRepository.class);
assertThat(sessionRepository).extracting("sessionIdGenerationStrategy")
.isInstanceOf(TestSessionIdGenerationStrategy.class);
}
@Test
void registerWhenNoSessionIdGenerationStrategyBeanThenDefault() {
registerAndRefresh(DefaultConfiguration.class);
HazelcastIndexedSessionRepository sessionRepository = this.context
.getBean(HazelcastIndexedSessionRepository.class);
assertThat(sessionRepository).extracting("sessionIdGenerationStrategy")
.isInstanceOf(UuidSessionIdGenerationStrategy.class);
}
private void registerAndRefresh(Class<?>... annotatedClasses) {
this.context.register(annotatedClasses);
this.context.refresh();
@@ -465,4 +485,23 @@ class HazelcastHttpSessionConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class SessionIdGenerationStrategyConfiguration {
@Bean
SessionIdGenerationStrategy sessionIdGenerationStrategy() {
return new TestSessionIdGenerationStrategy();
}
}
static class TestSessionIdGenerationStrategy implements SessionIdGenerationStrategy {
@Override
public String generate() {
return "test";
}
}
}

View File

@@ -62,6 +62,8 @@ import org.springframework.session.MapSession;
import org.springframework.session.PrincipalNameIndexResolver;
import org.springframework.session.SaveMode;
import org.springframework.session.Session;
import org.springframework.session.SessionIdGenerationStrategy;
import org.springframework.session.UuidSessionIdGenerationStrategy;
import org.springframework.transaction.support.TransactionOperations;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -252,6 +254,8 @@ public class JdbcIndexedSessionRepository implements
private ThreadPoolTaskScheduler taskScheduler;
private SessionIdGenerationStrategy sessionIdGenerationStrategy = UuidSessionIdGenerationStrategy.getInstance();
/**
* Create a new {@link JdbcIndexedSessionRepository} instance which uses the provided
* {@link JdbcOperations} and {@link TransactionOperations} to manage sessions.
@@ -461,7 +465,7 @@ public class JdbcIndexedSessionRepository implements
@Override
public JdbcSession createSession() {
MapSession delegate = new MapSession();
MapSession delegate = new MapSession(this.sessionIdGenerationStrategy);
delegate.setMaxInactiveInterval(this.defaultMaxInactiveInterval);
JdbcSession session = new JdbcSession(delegate, UUID.randomUUID().toString(), true);
session.flushIfRequired();
@@ -686,6 +690,16 @@ public class JdbcIndexedSessionRepository implements
TypeDescriptor.valueOf(Object.class));
}
/**
* Set the {@link SessionIdGenerationStrategy} to use to generate session ids.
* @param sessionIdGenerationStrategy the {@link SessionIdGenerationStrategy} to use
* @since 3.2
*/
public void setSessionIdGenerationStrategy(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
Assert.notNull(sessionIdGenerationStrategy, "sessionIdGenerationStrategy cannot be null");
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
}
private enum DeltaValue {
ADDED, UPDATED, REMOVED
@@ -721,7 +735,7 @@ public class JdbcIndexedSessionRepository implements
*/
final class JdbcSession implements Session {
private final Session delegate;
private final MapSession delegate;
private final String primaryKey;
@@ -773,7 +787,9 @@ public class JdbcIndexedSessionRepository implements
@Override
public String changeSessionId() {
this.changed = true;
return this.delegate.changeSessionId();
String newSessionId = JdbcIndexedSessionRepository.this.sessionIdGenerationStrategy.generate();
this.delegate.setId(newSessionId);
return newSessionId;
}
@Override

View File

@@ -50,6 +50,8 @@ import org.springframework.session.IndexResolver;
import org.springframework.session.MapSession;
import org.springframework.session.SaveMode;
import org.springframework.session.Session;
import org.springframework.session.SessionIdGenerationStrategy;
import org.springframework.session.UuidSessionIdGenerationStrategy;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
import org.springframework.session.jdbc.JdbcIndexedSessionRepository;
@@ -109,6 +111,8 @@ public class JdbcHttpSessionConfiguration implements BeanClassLoaderAware, Embed
private StringValueResolver embeddedValueResolver;
private SessionIdGenerationStrategy sessionIdGenerationStrategy = UuidSessionIdGenerationStrategy.getInstance();
@Bean
public JdbcIndexedSessionRepository sessionRepository() {
JdbcTemplate jdbcTemplate = createJdbcTemplate(this.dataSource);
@@ -144,6 +148,7 @@ public class JdbcHttpSessionConfiguration implements BeanClassLoaderAware, Embed
else {
sessionRepository.setConversionService(createConversionServiceWithBeanClassLoader(this.classLoader));
}
sessionRepository.setSessionIdGenerationStrategy(this.sessionIdGenerationStrategy);
this.sessionRepositoryCustomizers
.forEach((sessionRepositoryCustomizer) -> sessionRepositoryCustomizer.customize(sessionRepository));
return sessionRepository;
@@ -235,6 +240,11 @@ public class JdbcHttpSessionConfiguration implements BeanClassLoaderAware, Embed
this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList());
}
@Autowired(required = false)
public void setSessionIdGenerationStrategy(SessionIdGenerationStrategy sessionIdGenerationStrategy) {
this.sessionIdGenerationStrategy = sessionIdGenerationStrategy;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;

View File

@@ -0,0 +1,34 @@
/*
* 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.jdbc;
import org.springframework.session.SessionIdGenerationStrategy;
public class FixedSessionIdGenerationStrategy implements SessionIdGenerationStrategy {
private final String id;
public FixedSessionIdGenerationStrategy(String id) {
this.id = id;
}
@Override
public String generate() {
return this.id;
}
}

View File

@@ -263,6 +263,12 @@ class JdbcIndexedSessionRepositoryTests {
assertThat(this.repository).extracting("taskScheduler").isNull();
}
@Test
void setSessionIdGenerationStrategyWhenNullThenException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setSessionIdGenerationStrategy(null))
.withMessage("sessionIdGenerationStrategy cannot be null");
}
@Test
void createSessionDefaultMaxInactiveInterval() {
JdbcSession session = this.repository.createSession();
@@ -769,4 +775,32 @@ class JdbcIndexedSessionRepositoryTests {
verify(lobCreator, atLeastOnce()).close();
}
@Test
void createSessionWhenSessionIdGenerationStrategyThenUses() {
this.repository.setSessionIdGenerationStrategy(() -> "test");
JdbcSession session = this.repository.createSession();
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");
Session saved = this.repository.new JdbcSession(new MapSession(), "primaryKey", false);
saved.setAttribute("savedName", "savedValue");
given(this.jdbcOperations.query(isA(String.class), isA(PreparedStatementSetter.class),
isA(ResultSetExtractor.class))).willReturn(Collections.singletonList(saved));
JdbcSession session = this.repository.findById(saved.getId());
assertThat(session.getId()).isEqualTo(saved.getId());
assertThat(session.changeSessionId()).isEqualTo("test");
}
}

View File

@@ -43,7 +43,10 @@ import org.springframework.session.FlushMode;
import org.springframework.session.IndexResolver;
import org.springframework.session.SaveMode;
import org.springframework.session.Session;
import org.springframework.session.SessionIdGenerationStrategy;
import org.springframework.session.UuidSessionIdGenerationStrategy;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.jdbc.FixedSessionIdGenerationStrategy;
import org.springframework.session.jdbc.JdbcIndexedSessionRepository;
import org.springframework.session.jdbc.config.annotation.SpringSessionDataSource;
import org.springframework.test.util.ReflectionTestUtils;
@@ -318,11 +321,40 @@ class JdbcHttpSessionConfigurationTests {
assertThat(sessionRepository).extracting("defaultMaxInactiveInterval").isEqualTo(Duration.ZERO);
}
@Test
void sessionIdGenerationStrategyWhenCustomBeanThenUses() {
registerAndRefresh(DataSourceConfiguration.class, CustomSessionIdGenerationStrategyConfiguration.class);
JdbcIndexedSessionRepository sessionRepository = this.context.getBean(JdbcIndexedSessionRepository.class);
SessionIdGenerationStrategy sessionIdGenerationStrategy = (SessionIdGenerationStrategy) ReflectionTestUtils
.getField(sessionRepository, "sessionIdGenerationStrategy");
assertThat(sessionIdGenerationStrategy).isInstanceOf(FixedSessionIdGenerationStrategy.class);
}
@Test
void sessionIdGenerationStrategyWhenNoBeanThenDefault() {
registerAndRefresh(DataSourceConfiguration.class, DefaultConfiguration.class);
JdbcIndexedSessionRepository sessionRepository = this.context.getBean(JdbcIndexedSessionRepository.class);
SessionIdGenerationStrategy sessionIdGenerationStrategy = (SessionIdGenerationStrategy) ReflectionTestUtils
.getField(sessionRepository, "sessionIdGenerationStrategy");
assertThat(sessionIdGenerationStrategy).isInstanceOf(UuidSessionIdGenerationStrategy.class);
}
private void registerAndRefresh(Class<?>... annotatedClasses) {
this.context.register(annotatedClasses);
this.context.refresh();
}
@Configuration(proxyBeanMethods = false)
@EnableJdbcHttpSession
static class CustomSessionIdGenerationStrategyConfiguration {
@Bean
SessionIdGenerationStrategy sessionIdGenerationStrategy() {
return new FixedSessionIdGenerationStrategy("my-id");
}
}
@Configuration(proxyBeanMethods = false)
@EnableJdbcHttpSession
static class NoDataSourceConfiguration {