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

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