Leverage Spring Session's IndexResolver.

Resolves #104.
This commit is contained in:
Greg Turnquist
2019-09-27 13:06:06 -05:00
parent 1c7d5e5d91
commit db93654a6e
7 changed files with 173 additions and 108 deletions

View File

@@ -30,8 +30,10 @@ import org.springframework.data.mongodb.core.index.IndexInfo;
import org.springframework.data.mongodb.core.index.IndexOperations;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.lang.Nullable;
import org.springframework.session.DelegatingIndexResolver;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.IndexResolver;
import org.springframework.session.PrincipalNameIndexResolver;
import com.mongodb.DBObject;
@@ -49,6 +51,8 @@ public abstract class AbstractMongoSessionConverter implements GenericConverter
private static final Log LOG = LogFactory.getLog(AbstractMongoSessionConverter.class);
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
private IndexResolver<MongoSession> indexResolver = new DelegatingIndexResolver<>(new PrincipalNameIndexResolver<>());
/**
* Returns query to be executed to return sessions based on a particular index.
*
@@ -80,15 +84,9 @@ public abstract class AbstractMongoSessionConverter implements GenericConverter
.ensureIndex(new Index(EXPIRE_AT_FIELD_NAME, Sort.Direction.ASC).named(EXPIRE_AT_FIELD_NAME).expire(0));
}
protected String extractPrincipal(Session expiringSession) {
String resolvedPrincipal = AuthenticationParser.extractName(expiringSession.getAttribute(SPRING_SECURITY_CONTEXT));
if (resolvedPrincipal != null) {
return resolvedPrincipal;
} else {
return expiringSession.getAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME);
}
protected String extractPrincipal(MongoSession expiringSession) {
return this.indexResolver.resolveIndexesFor(expiringSession)
.get(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME);
}
public Set<ConvertiblePair> getConvertibleTypes() {
@@ -116,4 +114,8 @@ public abstract class AbstractMongoSessionConverter implements GenericConverter
protected abstract DBObject convert(MongoSession session);
protected abstract MongoSession convert(Document sessionWrapper);
public void setIndexResolver(IndexResolver<MongoSession> indexResolver) {
this.indexResolver = Assert.requireNonNull(indexResolver, "indexResolver must not be null!");
}
}

View File

@@ -1,53 +0,0 @@
/*
* Copyright 2014-2016 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.data.mongo;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.lang.Nullable;
/**
* Utility class to extract principal name from {@code Authentication} object.
*
* @author Jakub Kubrynski
* @author Greg Turnquist
*/
final class AuthenticationParser {
private static final String NAME_EXPRESSION = "authentication?.name";
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
private AuthenticationParser() {}
/**
* Extracts principal name from authentication.
*
* @param authentication Authentication object
* @return principal name
*/
@Nullable
static String extractName(@Nullable Object authentication) {
if (authentication == null) {
return null;
}
Expression expression = PARSER.parseExpression(NAME_EXPRESSION);
return expression.getValue(authentication, String.class);
}
}

View File

@@ -31,11 +31,13 @@ import org.springframework.core.serializer.support.DeserializingConverter;
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.session.IndexResolver;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
import org.springframework.session.data.mongo.AbstractMongoSessionConverter;
import org.springframework.session.data.mongo.JdkMongoSessionConverter;
import org.springframework.session.data.mongo.MongoIndexedSessionRepository;
import org.springframework.session.data.mongo.MongoSession;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;
@@ -57,6 +59,7 @@ public class MongoHttpSessionConfiguration extends SpringHttpSessionConfiguratio
private StringValueResolver embeddedValueResolver;
private List<SessionRepositoryCustomizer<MongoIndexedSessionRepository>> sessionRepositoryCustomizers;
private ClassLoader classLoader;
private IndexResolver<MongoSession> indexResolver;
@Bean
public MongoIndexedSessionRepository mongoSessionRepository(MongoOperations mongoOperations) {
@@ -66,10 +69,19 @@ public class MongoHttpSessionConfiguration extends SpringHttpSessionConfiguratio
if (this.mongoSessionConverter != null) {
repository.setMongoSessionConverter(this.mongoSessionConverter);
if (this.indexResolver != null) {
this.mongoSessionConverter.setIndexResolver(this.indexResolver);
}
} else {
JdkMongoSessionConverter mongoSessionConverter = new JdkMongoSessionConverter(new SerializingConverter(),
new DeserializingConverter(this.classLoader),
Duration.ofSeconds(MongoIndexedSessionRepository.DEFAULT_INACTIVE_INTERVAL));
if (this.indexResolver != null) {
mongoSessionConverter.setIndexResolver(this.indexResolver);
}
repository.setMongoSessionConverter(mongoSessionConverter);
}
@@ -129,4 +141,8 @@ public class MongoHttpSessionConfiguration extends SpringHttpSessionConfiguratio
this.embeddedValueResolver = resolver;
}
@Autowired(required = false)
public void setIndexResolver(IndexResolver<MongoSession> indexResolver) {
this.indexResolver = indexResolver;
}
}

View File

@@ -32,10 +32,12 @@ import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.session.IndexResolver;
import org.springframework.session.config.ReactiveSessionRepositoryCustomizer;
import org.springframework.session.config.annotation.web.server.SpringWebSessionConfiguration;
import org.springframework.session.data.mongo.AbstractMongoSessionConverter;
import org.springframework.session.data.mongo.JdkMongoSessionConverter;
import org.springframework.session.data.mongo.MongoSession;
import org.springframework.session.data.mongo.ReactiveMongoSessionRepository;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;
@@ -58,6 +60,8 @@ public class ReactiveMongoWebSessionConfiguration extends SpringWebSessionConfig
@Autowired(required = false) private MongoOperations mongoOperations;
private ClassLoader classLoader;
private IndexResolver<MongoSession> indexResolver;
@Bean
public ReactiveMongoSessionRepository reactiveMongoSessionRepository(ReactiveMongoOperations operations) {
@@ -66,10 +70,20 @@ public class ReactiveMongoWebSessionConfiguration extends SpringWebSessionConfig
if (this.mongoSessionConverter != null) {
repository.setMongoSessionConverter(this.mongoSessionConverter);
if (this.indexResolver != null) {
this.mongoSessionConverter.setIndexResolver(this.indexResolver);
}
} else {
JdkMongoSessionConverter mongoSessionConverter = new JdkMongoSessionConverter(new SerializingConverter(),
new DeserializingConverter(this.classLoader),
Duration.ofSeconds(ReactiveMongoSessionRepository.DEFAULT_INACTIVE_INTERVAL));
if (this.indexResolver != null) {
mongoSessionConverter.setIndexResolver(this.indexResolver);
}
repository.setMongoSessionConverter(mongoSessionConverter);
}
@@ -146,4 +160,9 @@ public class ReactiveMongoWebSessionConfiguration extends SpringWebSessionConfig
ObjectProvider<ReactiveSessionRepositoryCustomizer<ReactiveMongoSessionRepository>> sessionRepositoryCustomizers) {
this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList());
}
@Autowired(required = false)
public void setIndexResolver(IndexResolver<MongoSession> indexResolver) {
this.indexResolver = indexResolver;
}
}

View File

@@ -1,45 +0,0 @@
/*
* Copyright 2014-2016 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.data.mongo;
import static org.assertj.core.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextImpl;
/**
* @author Jakub Kubrynski
* @author Greg Turnquist
*/
public class AuthenticationParserTest {
@Test
public void shouldExtractName() {
// given
String principalName = "john_the_springer";
SecurityContextImpl context = new SecurityContextImpl();
context.setAuthentication(new UsernamePasswordAuthenticationToken(principalName, null));
// when
String extractedName = AuthenticationParser.extractName(context);
// then
assertThat(extractedName).isEqualTo(principalName);
}
}

View File

@@ -33,9 +33,12 @@ import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.index.IndexOperations;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.session.IndexResolver;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.data.mongo.AbstractMongoSessionConverter;
import org.springframework.session.data.mongo.JacksonMongoSessionConverter;
import org.springframework.session.data.mongo.MongoIndexedSessionRepository;
import org.springframework.session.data.mongo.MongoSession;
import org.springframework.test.util.ReflectionTestUtils;
/**
@@ -156,6 +159,32 @@ public class MongoHttpSessionConfigurationTest {
assertThat(sessionRepository).hasFieldOrPropertyWithValue("maxInactiveIntervalInSeconds", 10000);
}
@Test
void customIndexResolverConfigurationWithDefaultMongoSessionConverter() {
registerAndRefresh(MongoConfiguration.class, CustomIndexResolverConfigurationWithDefaultMongoSessionConverter.class);
MongoIndexedSessionRepository repository = this.context.getBean(MongoIndexedSessionRepository.class);
IndexResolver<MongoSession> indexResolver = this.context.getBean(IndexResolver.class);
assertThat(repository).isNotNull();
assertThat(indexResolver).isNotNull();
assertThat(repository).extracting("mongoSessionConverter").hasFieldOrPropertyWithValue("indexResolver", indexResolver);
}
@Test
void customIndexResolverConfigurationWithProvidedMongoSessionConverter() {
registerAndRefresh(MongoConfiguration.class, CustomIndexResolverConfigurationWithProvidedMongoSessionConverter.class);
MongoIndexedSessionRepository repository = this.context.getBean(MongoIndexedSessionRepository.class);
IndexResolver<MongoSession> indexResolver = this.context.getBean(IndexResolver.class);
assertThat(repository).isNotNull();
assertThat(indexResolver).isNotNull();
assertThat(repository).extracting("mongoSessionConverter").hasFieldOrPropertyWithValue("indexResolver", indexResolver);
}
private void registerAndRefresh(Class<?>... annotatedClasses) {
this.context.register(annotatedClasses);
@@ -264,4 +293,30 @@ public class MongoHttpSessionConfigurationTest {
}
}
@Configuration
@EnableMongoHttpSession
static class CustomIndexResolverConfigurationWithDefaultMongoSessionConverter {
@Bean
@SuppressWarnings("unchecked")
public IndexResolver<MongoSession> indexResolver() {
return mock(IndexResolver.class);
}
}
@Configuration
@EnableMongoHttpSession
static class CustomIndexResolverConfigurationWithProvidedMongoSessionConverter {
@Bean
public AbstractMongoSessionConverter mongoSessionConverter() {
return new JacksonMongoSessionConverter();
}
@Bean
@SuppressWarnings("unchecked")
public IndexResolver<MongoSession> indexResolver() {
return mock(IndexResolver.class);
}
}
}

View File

@@ -30,12 +30,14 @@ import org.springframework.context.annotation.Bean;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.data.mongodb.core.index.IndexOperations;
import org.springframework.session.IndexResolver;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.config.ReactiveSessionRepositoryCustomizer;
import org.springframework.session.config.annotation.web.server.EnableSpringWebSession;
import org.springframework.session.data.mongo.AbstractMongoSessionConverter;
import org.springframework.session.data.mongo.JacksonMongoSessionConverter;
import org.springframework.session.data.mongo.JdkMongoSessionConverter;
import org.springframework.session.data.mongo.MongoSession;
import org.springframework.session.data.mongo.ReactiveMongoSessionRepository;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
@@ -177,6 +179,37 @@ public class ReactiveMongoWebSessionConfigurationTest {
assertThat(repository).hasFieldOrPropertyWithValue("maxInactiveIntervalInSeconds", 10000);
}
@Test
void customIndexResolverConfigurationWithDefaultMongoSessionConverter() {
this.context = new AnnotationConfigApplicationContext();
this.context.register(CustomIndexResolverConfigurationWithDefaultMongoSessionConverter.class);
this.context.refresh();
ReactiveMongoSessionRepository repository = this.context.getBean(ReactiveMongoSessionRepository.class);
IndexResolver<MongoSession> indexResolver = this.context.getBean(IndexResolver.class);
assertThat(repository).isNotNull();
assertThat(indexResolver).isNotNull();
assertThat(repository).extracting("mongoSessionConverter").hasFieldOrPropertyWithValue("indexResolver", indexResolver);
}
@Test
void customIndexResolverConfigurationWithProvidedMongoSessionConverter() {
this.context = new AnnotationConfigApplicationContext();
this.context.register(CustomIndexResolverConfigurationWithProvidedtMongoSessionConverter.class);
this.context.refresh();
ReactiveMongoSessionRepository repository = this.context.getBean(ReactiveMongoSessionRepository.class);
IndexResolver<MongoSession> indexResolver = this.context.getBean(IndexResolver.class);
assertThat(repository).isNotNull();
assertThat(indexResolver).isNotNull();
assertThat(repository).extracting("mongoSessionConverter").hasFieldOrPropertyWithValue("indexResolver", indexResolver);
}
/**
* Reflectively extract the {@link AbstractMongoSessionConverter} from the {@link ReactiveMongoSessionRepository}.
* This is to avoid expanding the surface area of the API.
@@ -299,4 +332,42 @@ public class ReactiveMongoWebSessionConfigurationTest {
}
}
@EnableMongoWebSession
static class CustomIndexResolverConfigurationWithDefaultMongoSessionConverter {
@Bean
ReactiveMongoOperations operations() {
return mock(ReactiveMongoOperations.class);
}
@Bean
@SuppressWarnings("unchecked")
public IndexResolver<MongoSession> indexResolver() {
return mock(IndexResolver.class);
}
}
@EnableMongoWebSession
static class CustomIndexResolverConfigurationWithProvidedtMongoSessionConverter {
@Bean
ReactiveMongoOperations operations() {
return mock(ReactiveMongoOperations.class);
}
@Bean
JacksonMongoSessionConverter jacksonMongoSessionConverter() {
return new JacksonMongoSessionConverter();
}
@Bean
@SuppressWarnings("unchecked")
public IndexResolver<MongoSession> indexResolver() {
return mock(IndexResolver.class);
}
}
}