Remove session store type in favor of defined order

Closes gh-27756
pull/31554/head
Madhura Bhave 2 years ago
parent 6346f21f4e
commit 7cb53b3c45

@ -1,69 +0,0 @@
/*
* Copyright 2012-2019 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.boot.autoconfigure.session;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.context.properties.bind.BindException;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.AnnotationMetadata;
/**
* Base class for Servlet and reactive session conditions.
*
* @author Tommy Ludwig
* @author Stephane Nicoll
* @author Madhura Bhave
* @author Andy Wilkinson
*/
abstract class AbstractSessionCondition extends SpringBootCondition {
private final WebApplicationType webApplicationType;
protected AbstractSessionCondition(WebApplicationType webApplicationType) {
this.webApplicationType = webApplicationType;
}
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage.forCondition("Session Condition");
Environment environment = context.getEnvironment();
StoreType required = SessionStoreMappings.getType(this.webApplicationType,
((AnnotationMetadata) metadata).getClassName());
if (!environment.containsProperty("spring.session.store-type")) {
return ConditionOutcome.match(message.didNotFind("property", "properties")
.items(ConditionMessage.Style.QUOTE, "spring.session.store-type"));
}
try {
Binder binder = Binder.get(environment);
return binder.bind("spring.session.store-type", StoreType.class)
.map((t) -> new ConditionOutcome(t == required,
message.found("spring.session.store-type property").items(t)))
.orElseGet(() -> ConditionOutcome
.noMatch(message.didNotFind("spring.session.store-type property").atAll()));
}
catch (BindException ex) {
return ConditionOutcome.noMatch(message.found("invalid spring.session.store-type property").atAll());
}
}
}

@ -26,7 +26,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.session.SessionRepository; import org.springframework.session.SessionRepository;
import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository; import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository;
@ -44,7 +43,6 @@ import org.springframework.session.hazelcast.config.annotation.web.http.Hazelcas
@ConditionalOnClass(HazelcastIndexedSessionRepository.class) @ConditionalOnClass(HazelcastIndexedSessionRepository.class)
@ConditionalOnMissingBean(SessionRepository.class) @ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnBean(HazelcastInstance.class) @ConditionalOnBean(HazelcastInstance.class)
@Conditional(ServletSessionCondition.class)
@EnableConfigurationProperties(HazelcastSessionProperties.class) @EnableConfigurationProperties(HazelcastSessionProperties.class)
class HazelcastSessionConfiguration { class HazelcastSessionConfiguration {

@ -50,7 +50,6 @@ import org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessi
@ConditionalOnClass({ JdbcTemplate.class, JdbcIndexedSessionRepository.class }) @ConditionalOnClass({ JdbcTemplate.class, JdbcIndexedSessionRepository.class })
@ConditionalOnMissingBean(SessionRepository.class) @ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnBean(DataSource.class) @ConditionalOnBean(DataSource.class)
@Conditional(ServletSessionCondition.class)
@EnableConfigurationProperties(JdbcSessionProperties.class) @EnableConfigurationProperties(JdbcSessionProperties.class)
@Import(DatabaseInitializationDependencyConfigurer.class) @Import(DatabaseInitializationDependencyConfigurer.class)
class JdbcSessionConfiguration { class JdbcSessionConfiguration {

@ -24,7 +24,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.ReactiveMongoOperations; import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.session.ReactiveSessionRepository; import org.springframework.session.ReactiveSessionRepository;
@ -41,7 +40,6 @@ import org.springframework.session.data.mongo.config.annotation.web.reactive.Rea
@ConditionalOnClass({ ReactiveMongoOperations.class, ReactiveMongoSessionRepository.class }) @ConditionalOnClass({ ReactiveMongoOperations.class, ReactiveMongoSessionRepository.class })
@ConditionalOnMissingBean(ReactiveSessionRepository.class) @ConditionalOnMissingBean(ReactiveSessionRepository.class)
@ConditionalOnBean(ReactiveMongoOperations.class) @ConditionalOnBean(ReactiveMongoOperations.class)
@Conditional(ReactiveSessionCondition.class)
@EnableConfigurationProperties(MongoSessionProperties.class) @EnableConfigurationProperties(MongoSessionProperties.class)
class MongoReactiveSessionConfiguration { class MongoReactiveSessionConfiguration {

@ -24,7 +24,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.session.SessionRepository; import org.springframework.session.SessionRepository;
@ -41,7 +40,6 @@ import org.springframework.session.data.mongo.config.annotation.web.http.MongoHt
@ConditionalOnClass({ MongoOperations.class, MongoIndexedSessionRepository.class }) @ConditionalOnClass({ MongoOperations.class, MongoIndexedSessionRepository.class })
@ConditionalOnMissingBean(SessionRepository.class) @ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnBean(MongoOperations.class) @ConditionalOnBean(MongoOperations.class)
@Conditional(ServletSessionCondition.class)
@EnableConfigurationProperties(MongoSessionProperties.class) @EnableConfigurationProperties(MongoSessionProperties.class)
class MongoSessionConfiguration { class MongoSessionConfiguration {

@ -1,35 +0,0 @@
/*
* Copyright 2012-2019 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.boot.autoconfigure.session;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.ReactiveSessionRepository;
/**
* No-op session configuration used to disable Spring Session using the environment.
*
* @author Tommy Ludwig
* @author Andy Wilkinson
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(ReactiveSessionRepository.class)
@Conditional(ReactiveSessionCondition.class)
class NoOpReactiveSessionConfiguration {
}

@ -1,34 +0,0 @@
/*
* Copyright 2012-2019 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.boot.autoconfigure.session;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.SessionRepository;
/**
* No-op session configuration used to disable Spring Session using the environment.
*
* @author Tommy Ludwig
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(SessionRepository.class)
@Conditional(ServletSessionCondition.class)
class NoOpSessionConfiguration {
}

@ -1,47 +0,0 @@
/*
* Copyright 2012-2019 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.boot.autoconfigure.session;
import java.util.Collections;
import java.util.List;
import org.springframework.session.SessionRepository;
import org.springframework.util.ObjectUtils;
/**
* Exception thrown when multiple {@link SessionRepository} implementations are available
* with no way to know which implementation should be used.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
public class NonUniqueSessionRepositoryException extends RuntimeException {
private final List<Class<?>> availableCandidates;
public NonUniqueSessionRepositoryException(List<Class<?>> availableCandidates) {
super("Multiple session repository candidates are available, set the "
+ "'spring.session.store-type' property accordingly");
this.availableCandidates = (!ObjectUtils.isEmpty(availableCandidates) ? availableCandidates
: Collections.emptyList());
}
public List<Class<?>> getAvailableCandidates() {
return this.availableCandidates;
}
}

@ -1,45 +0,0 @@
/*
* Copyright 2012-2021 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.boot.autoconfigure.session;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
/**
* An {@link AbstractFailureAnalyzer} for {@link NonUniqueSessionRepositoryException}.
*
* @author Stephane Nicoll
*/
class NonUniqueSessionRepositoryFailureAnalyzer extends AbstractFailureAnalyzer<NonUniqueSessionRepositoryException> {
@Override
protected FailureAnalysis analyze(Throwable rootFailure, NonUniqueSessionRepositoryException cause) {
StringBuilder message = new StringBuilder();
message.append(
String.format("Multiple Spring Session store implementations are available on the classpath:%n"));
for (Class<?> candidate : cause.getAvailableCandidates()) {
message.append(String.format(" - %s%n", candidate.getName()));
}
StringBuilder action = new StringBuilder();
action.append(String.format("Consider any of the following:%n"));
action.append(
String.format(" - Define the 'spring.session.store-type' property to the store you want to use%n"));
action.append(String.format(" - Review your classpath and remove the unwanted store implementation(s)%n"));
return new FailureAnalysis(message.toString(), action.toString(), cause);
}
}

@ -1,32 +0,0 @@
/*
* Copyright 2012-2019 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.boot.autoconfigure.session;
import org.springframework.boot.WebApplicationType;
/**
* General condition used with all reactive session configuration classes.
*
* @author Andy Wilkinson
*/
class ReactiveSessionCondition extends AbstractSessionCondition {
ReactiveSessionCondition() {
super(WebApplicationType.REACTIVE);
}
}

@ -24,7 +24,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
import org.springframework.session.ReactiveSessionRepository; import org.springframework.session.ReactiveSessionRepository;
@ -41,7 +40,6 @@ import org.springframework.session.data.redis.config.annotation.web.server.Redis
@ConditionalOnClass({ ReactiveRedisConnectionFactory.class, ReactiveRedisSessionRepository.class }) @ConditionalOnClass({ ReactiveRedisConnectionFactory.class, ReactiveRedisSessionRepository.class })
@ConditionalOnMissingBean(ReactiveSessionRepository.class) @ConditionalOnMissingBean(ReactiveSessionRepository.class)
@ConditionalOnBean(ReactiveRedisConnectionFactory.class) @ConditionalOnBean(ReactiveRedisConnectionFactory.class)
@Conditional(ReactiveSessionCondition.class)
@EnableConfigurationProperties(RedisSessionProperties.class) @EnableConfigurationProperties(RedisSessionProperties.class)
class RedisReactiveSessionConfiguration { class RedisReactiveSessionConfiguration {

@ -25,7 +25,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
@ -48,7 +47,6 @@ import org.springframework.session.data.redis.config.annotation.web.http.RedisIn
@ConditionalOnClass({ RedisTemplate.class, RedisIndexedSessionRepository.class }) @ConditionalOnClass({ RedisTemplate.class, RedisIndexedSessionRepository.class })
@ConditionalOnMissingBean(SessionRepository.class) @ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnBean(RedisConnectionFactory.class) @ConditionalOnBean(RedisConnectionFactory.class)
@Conditional(ServletSessionCondition.class)
@EnableConfigurationProperties(RedisSessionProperties.class) @EnableConfigurationProperties(RedisSessionProperties.class)
class RedisSessionConfiguration { class RedisSessionConfiguration {

@ -1,35 +0,0 @@
/*
* Copyright 2012-2019 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.boot.autoconfigure.session;
import org.springframework.boot.WebApplicationType;
/**
* General condition used with all servlet session configuration classes.
*
* @author Tommy Ludwig
* @author Stephane Nicoll
* @author Madhura Bhave
* @author Andy Wilkinson
*/
class ServletSessionCondition extends AbstractSessionCondition {
ServletSessionCondition() {
super(WebApplicationType.SERVLET);
}
}

@ -17,14 +17,8 @@
package org.springframework.boot.autoconfigure.session; package org.springframework.boot.autoconfigure.session;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
@ -49,13 +43,10 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.web.server.Cookie.SameSite; import org.springframework.boot.web.server.Cookie.SameSite;
import org.springframework.boot.web.servlet.server.Session.Cookie; import org.springframework.boot.web.servlet.server.Session.Cookie;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.web.authentication.RememberMeServices; import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.session.ReactiveSessionRepository; import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.Session; import org.springframework.session.Session;
@ -90,7 +81,7 @@ public class SessionAutoConfiguration {
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnWebApplication(type = Type.SERVLET)
@Import({ ServletSessionRepositoryValidator.class, SessionRepositoryFilterConfiguration.class }) @Import(SessionRepositoryFilterConfiguration.class)
static class ServletSessionConfiguration { static class ServletSessionConfiguration {
@Bean @Bean
@ -125,8 +116,8 @@ public class SessionAutoConfiguration {
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(SessionRepository.class) @ConditionalOnMissingBean(SessionRepository.class)
@Import({ ServletSessionRepositoryImplementationValidator.class, @Import({ RedisSessionConfiguration.class, MongoSessionConfiguration.class, HazelcastSessionConfiguration.class,
ServletSessionConfigurationImportSelector.class }) JdbcSessionConfiguration.class })
static class ServletSessionRepositoryConfiguration { static class ServletSessionRepositoryConfiguration {
} }
@ -135,17 +126,10 @@ public class SessionAutoConfiguration {
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.REACTIVE) @ConditionalOnWebApplication(type = Type.REACTIVE)
@Import(ReactiveSessionRepositoryValidator.class) @ConditionalOnMissingBean(ReactiveSessionRepository.class)
@Import({ RedisReactiveSessionConfiguration.class, MongoReactiveSessionConfiguration.class })
static class ReactiveSessionConfiguration { static class ReactiveSessionConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(ReactiveSessionRepository.class)
@Import({ ReactiveSessionRepositoryImplementationValidator.class,
ReactiveSessionConfigurationImportSelector.class })
static class ReactiveSessionRepositoryConfiguration {
}
} }
/** /**
@ -173,175 +157,4 @@ public class SessionAutoConfiguration {
} }
/**
* {@link ImportSelector} base class to add {@link StoreType} configuration classes.
*/
abstract static class SessionConfigurationImportSelector implements ImportSelector {
protected final String[] selectImports(WebApplicationType webApplicationType) {
return Arrays.stream(StoreType.values())
.map((type) -> SessionStoreMappings.getConfigurationClass(webApplicationType, type))
.toArray(String[]::new);
}
}
/**
* {@link ImportSelector} to add {@link StoreType} configuration classes for reactive
* web applications.
*/
static class ReactiveSessionConfigurationImportSelector extends SessionConfigurationImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return super.selectImports(WebApplicationType.REACTIVE);
}
}
/**
* {@link ImportSelector} to add {@link StoreType} configuration classes for Servlet
* web applications.
*/
static class ServletSessionConfigurationImportSelector extends SessionConfigurationImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return super.selectImports(WebApplicationType.SERVLET);
}
}
/**
* Base class for beans used to validate that only one supported implementation is
* available in the classpath when the store-type property is not set.
*/
abstract static class AbstractSessionRepositoryImplementationValidator {
private final List<String> candidates;
private final ClassLoader classLoader;
private final SessionProperties sessionProperties;
AbstractSessionRepositoryImplementationValidator(ApplicationContext applicationContext,
SessionProperties sessionProperties, List<String> candidates) {
this.classLoader = applicationContext.getClassLoader();
this.sessionProperties = sessionProperties;
this.candidates = candidates;
checkAvailableImplementations();
}
private void checkAvailableImplementations() {
List<Class<?>> availableCandidates = new ArrayList<>();
for (String candidate : this.candidates) {
addCandidateIfAvailable(availableCandidates, candidate);
}
StoreType storeType = this.sessionProperties.getStoreType();
if (availableCandidates.size() > 1 && storeType == null) {
throw new NonUniqueSessionRepositoryException(availableCandidates);
}
}
private void addCandidateIfAvailable(List<Class<?>> candidates, String type) {
try {
candidates.add(Class.forName(type, false, this.classLoader));
}
catch (Throwable ex) {
// Ignore
}
}
}
/**
* Bean used to validate that only one supported implementation is available in the
* classpath when the store-type property is not set.
*/
static class ServletSessionRepositoryImplementationValidator
extends AbstractSessionRepositoryImplementationValidator {
ServletSessionRepositoryImplementationValidator(ApplicationContext applicationContext,
SessionProperties sessionProperties) {
super(applicationContext, sessionProperties,
Arrays.asList("org.springframework.session.hazelcast.HazelcastIndexedSessionRepository",
"org.springframework.session.jdbc.JdbcIndexedSessionRepository",
"org.springframework.session.data.mongo.MongoIndexedSessionRepository",
"org.springframework.session.data.redis.RedisIndexedSessionRepository"));
}
}
/**
* Bean used to validate that only one supported implementation is available in the
* classpath when the store-type property is not set.
*/
static class ReactiveSessionRepositoryImplementationValidator
extends AbstractSessionRepositoryImplementationValidator {
ReactiveSessionRepositoryImplementationValidator(ApplicationContext applicationContext,
SessionProperties sessionProperties) {
super(applicationContext, sessionProperties,
Arrays.asList("org.springframework.session.data.redis.ReactiveRedisSessionRepository",
"org.springframework.session.data.mongo.ReactiveMongoSessionRepository"));
}
}
/**
* Base class for validating that a (reactive) session repository bean exists.
*/
abstract static class AbstractSessionRepositoryValidator implements InitializingBean {
private final SessionProperties sessionProperties;
private final ObjectProvider<?> sessionRepositoryProvider;
protected AbstractSessionRepositoryValidator(SessionProperties sessionProperties,
ObjectProvider<?> sessionRepositoryProvider) {
this.sessionProperties = sessionProperties;
this.sessionRepositoryProvider = sessionRepositoryProvider;
}
@Override
public void afterPropertiesSet() {
StoreType storeType = this.sessionProperties.getStoreType();
if (storeType != StoreType.NONE && this.sessionRepositoryProvider.getIfAvailable() == null
&& storeType != null) {
throw new SessionRepositoryUnavailableException(
"No session repository could be auto-configured, check your "
+ "configuration (session store type is '"
+ storeType.name().toLowerCase(Locale.ENGLISH) + "')",
storeType);
}
}
}
/**
* Bean used to validate that a {@link SessionRepository} exists and provide a
* meaningful message if that's not the case.
*/
static class ServletSessionRepositoryValidator extends AbstractSessionRepositoryValidator {
ServletSessionRepositoryValidator(SessionProperties sessionProperties,
ObjectProvider<SessionRepository<?>> sessionRepositoryProvider) {
super(sessionProperties, sessionRepositoryProvider);
}
}
/**
* Bean used to validate that a {@link ReactiveSessionRepository} exists and provide a
* meaningful message if that's not the case.
*/
static class ReactiveSessionRepositoryValidator extends AbstractSessionRepositoryValidator {
ReactiveSessionRepositoryValidator(SessionProperties sessionProperties,
ObjectProvider<ReactiveSessionRepository<?>> sessionRepositoryProvider) {
super(sessionProperties, sessionRepositoryProvider);
}
}
} }

@ -39,11 +39,6 @@ import org.springframework.session.web.http.SessionRepositoryFilter;
@ConfigurationProperties(prefix = "spring.session") @ConfigurationProperties(prefix = "spring.session")
public class SessionProperties { public class SessionProperties {
/**
* Session store type.
*/
private StoreType storeType;
/** /**
* Session timeout. If a duration suffix is not specified, seconds will be used. * Session timeout. If a duration suffix is not specified, seconds will be used.
*/ */
@ -52,14 +47,6 @@ public class SessionProperties {
private Servlet servlet = new Servlet(); private Servlet servlet = new Servlet();
public StoreType getStoreType() {
return this.storeType;
}
public void setStoreType(StoreType storeType) {
this.storeType = storeType;
}
public Duration getTimeout() { public Duration getTimeout() {
return this.timeout; return this.timeout;
} }

@ -1,40 +0,0 @@
/*
* Copyright 2012-2019 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.boot.autoconfigure.session;
import org.springframework.session.SessionRepository;
/**
* Exception thrown when no {@link SessionRepository} is available.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
public class SessionRepositoryUnavailableException extends RuntimeException {
private final StoreType storeType;
public SessionRepositoryUnavailableException(String message, StoreType storeType) {
super(message);
this.storeType = storeType;
}
public StoreType getStoreType() {
return this.storeType;
}
}

@ -1,94 +0,0 @@
/*
* Copyright 2012-2019 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.boot.autoconfigure.session;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Map;
import org.springframework.boot.WebApplicationType;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Mappings between {@link StoreType} and {@code @Configuration}.
*
* @author Tommy Ludwig
* @author Eddú Meléndez
*/
final class SessionStoreMappings {
private static final Map<StoreType, Configurations> MAPPINGS;
static {
Map<StoreType, Configurations> mappings = new EnumMap<>(StoreType.class);
mappings.put(StoreType.REDIS,
new Configurations(RedisSessionConfiguration.class, RedisReactiveSessionConfiguration.class));
mappings.put(StoreType.MONGODB,
new Configurations(MongoSessionConfiguration.class, MongoReactiveSessionConfiguration.class));
mappings.put(StoreType.JDBC, new Configurations(JdbcSessionConfiguration.class, null));
mappings.put(StoreType.HAZELCAST, new Configurations(HazelcastSessionConfiguration.class, null));
mappings.put(StoreType.NONE,
new Configurations(NoOpSessionConfiguration.class, NoOpReactiveSessionConfiguration.class));
MAPPINGS = Collections.unmodifiableMap(mappings);
}
private SessionStoreMappings() {
}
static String getConfigurationClass(WebApplicationType webApplicationType, StoreType sessionStoreType) {
Configurations configurations = MAPPINGS.get(sessionStoreType);
Assert.state(configurations != null, () -> "Unknown session store type " + sessionStoreType);
return configurations.getConfiguration(webApplicationType);
}
static StoreType getType(WebApplicationType webApplicationType, String configurationClass) {
return MAPPINGS.entrySet().stream()
.filter((entry) -> ObjectUtils.nullSafeEquals(configurationClass,
entry.getValue().getConfiguration(webApplicationType)))
.map(Map.Entry::getKey).findFirst()
.orElseThrow(() -> new IllegalStateException("Unknown configuration class " + configurationClass));
}
private static class Configurations {
private final Class<?> servletConfiguration;
private final Class<?> reactiveConfiguration;
Configurations(Class<?> servletConfiguration, Class<?> reactiveConfiguration) {
this.servletConfiguration = servletConfiguration;
this.reactiveConfiguration = reactiveConfiguration;
}
String getConfiguration(WebApplicationType webApplicationType) {
switch (webApplicationType) {
case SERVLET:
return getName(this.servletConfiguration);
case REACTIVE:
return getName(this.reactiveConfiguration);
}
return null;
}
String getName(Class<?> configuration) {
return (configuration != null) ? configuration.getName() : null;
}
}
}

@ -1,54 +0,0 @@
/*
* Copyright 2012-2019 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.boot.autoconfigure.session;
/**
* Supported Spring Session data store types.
*
* @author Tommy Ludwig
* @author Eddú Meléndez
* @author Vedran Pavic
* @since 1.4.0
*/
public enum StoreType {
/**
* Redis backed sessions.
*/
REDIS,
/**
* MongoDB backed sessions.
*/
MONGODB,
/**
* JDBC backed sessions.
*/
JDBC,
/**
* Hazelcast backed sessions.
*/
HAZELCAST,
/**
* No session data-store.
*/
NONE
}

@ -32,8 +32,7 @@ org.springframework.boot.autoconfigure.jooq.NoDslContextBeanFailureAnalyzer,\
org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryBeanCreationFailureAnalyzer,\ org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.r2dbc.MissingR2dbcPoolDependencyFailureAnalyzer,\ org.springframework.boot.autoconfigure.r2dbc.MissingR2dbcPoolDependencyFailureAnalyzer,\
org.springframework.boot.autoconfigure.r2dbc.MultipleConnectionPoolConfigurationsFailureAnalzyer,\ org.springframework.boot.autoconfigure.r2dbc.MultipleConnectionPoolConfigurationsFailureAnalzyer,\
org.springframework.boot.autoconfigure.r2dbc.NoConnectionFactoryBeanFailureAnalyzer,\ org.springframework.boot.autoconfigure.r2dbc.NoConnectionFactoryBeanFailureAnalyzer
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer
# Template availability providers # Template availability providers
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\ org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\

@ -1,65 +0,0 @@
/*
* Copyright 2012-2020 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.boot.autoconfigure.session;
import java.util.Arrays;
import org.junit.jupiter.api.Test;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.boot.diagnostics.FailureAnalyzer;
import org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter;
import org.springframework.session.SessionRepository;
import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository;
import org.springframework.session.jdbc.JdbcIndexedSessionRepository;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link NonUniqueSessionRepositoryFailureAnalyzer}.
*
* @author Stephane Nicoll
*/
class NonUniqueSessionRepositoryFailureAnalyzerTests {
private final FailureAnalyzer analyzer = new NonUniqueSessionRepositoryFailureAnalyzer();
@Test
void failureAnalysisWithMultipleCandidates() {
FailureAnalysis analysis = analyzeFailure(
createFailure(JdbcIndexedSessionRepository.class, HazelcastIndexedSessionRepository.class));
assertThat(analysis).isNotNull();
assertThat(analysis.getDescription()).contains(JdbcIndexedSessionRepository.class.getName(),
HazelcastIndexedSessionRepository.class.getName());
assertThat(analysis.getAction()).contains("spring.session.store-type");
}
@SafeVarargs
@SuppressWarnings("varargs")
private final Exception createFailure(Class<? extends SessionRepository<?>>... candidates) {
return new NonUniqueSessionRepositoryException(Arrays.asList(candidates));
}
private FailureAnalysis analyzeFailure(Exception failure) {
FailureAnalysis analysis = this.analyzer.analyze(failure);
if (analysis != null) {
new LoggingFailureAnalysisReporter().report(analysis);
}
return analysis;
}
}

@ -55,35 +55,21 @@ class ReactiveSessionAutoConfigurationMongoTests extends AbstractSessionAutoConf
.withStartupTimeout(Duration.ofMinutes(5)); .withStartupTimeout(Duration.ofMinutes(5));
private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class)); .withClassLoader(new FilteredClassLoader(ReactiveRedisSessionRepository.class))
.withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class, MongoAutoConfiguration.class,
MongoDataAutoConfiguration.class, MongoReactiveAutoConfiguration.class,
MongoReactiveDataAutoConfiguration.class));
@Test @Test
void defaultConfig() { void defaultConfig() {
this.contextRunner this.contextRunner.withPropertyValues("spring.data.mongodb.uri=" + mongoDb.getReplicaSetUrl())
.withPropertyValues("spring.session.store-type=mongodb",
"spring.data.mongodb.uri=" + mongoDb.getReplicaSetUrl())
.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class,
MongoReactiveAutoConfiguration.class, MongoReactiveDataAutoConfiguration.class))
.run(validateSpringSessionUsesMongo("sessions"));
}
@Test
void defaultConfigWithUniqueStoreImplementation() {
this.contextRunner.withClassLoader(new FilteredClassLoader(ReactiveRedisSessionRepository.class))
.withPropertyValues("spring.data.mongodb.uri=" + mongoDb.getReplicaSetUrl())
.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class,
MongoReactiveAutoConfiguration.class, MongoReactiveDataAutoConfiguration.class))
.run(validateSpringSessionUsesMongo("sessions")); .run(validateSpringSessionUsesMongo("sessions"));
} }
@Test @Test
void defaultConfigWithCustomTimeout() { void defaultConfigWithCustomTimeout() {
this.contextRunner this.contextRunner.withPropertyValues("spring.session.timeout=1m",
.withPropertyValues("spring.session.store-type=mongodb", "spring.session.timeout=1m", "spring.data.mongodb.uri=" + mongoDb.getReplicaSetUrl()).run((context) -> {
"spring.data.mongodb.uri=" + mongoDb.getReplicaSetUrl())
.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class,
MongoReactiveAutoConfiguration.class, MongoReactiveDataAutoConfiguration.class))
.run((context) -> {
ReactiveMongoSessionRepository repository = validateSessionRepository(context, ReactiveMongoSessionRepository repository = validateSessionRepository(context,
ReactiveMongoSessionRepository.class); ReactiveMongoSessionRepository.class);
assertThat(repository).hasFieldOrPropertyWithValue("maxInactiveIntervalInSeconds", 60); assertThat(repository).hasFieldOrPropertyWithValue("maxInactiveIntervalInSeconds", 60);
@ -92,12 +78,8 @@ class ReactiveSessionAutoConfigurationMongoTests extends AbstractSessionAutoConf
@Test @Test
void defaultConfigWithCustomSessionTimeout() { void defaultConfigWithCustomSessionTimeout() {
this.contextRunner this.contextRunner.withPropertyValues("server.reactive.session.timeout=1m",
.withPropertyValues("spring.session.store-type=mongodb", "server.reactive.session.timeout=1m", "spring.data.mongodb.uri=" + mongoDb.getReplicaSetUrl()).run((context) -> {
"spring.data.mongodb.uri=" + mongoDb.getReplicaSetUrl())
.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class,
MongoReactiveAutoConfiguration.class, MongoReactiveDataAutoConfiguration.class))
.run((context) -> {
ReactiveMongoSessionRepository repository = validateSessionRepository(context, ReactiveMongoSessionRepository repository = validateSessionRepository(context,
ReactiveMongoSessionRepository.class); ReactiveMongoSessionRepository.class);
assertThat(repository).hasFieldOrPropertyWithValue("maxInactiveIntervalInSeconds", 60); assertThat(repository).hasFieldOrPropertyWithValue("maxInactiveIntervalInSeconds", 60);
@ -107,24 +89,26 @@ class ReactiveSessionAutoConfigurationMongoTests extends AbstractSessionAutoConf
@Test @Test
void mongoSessionStoreWithCustomizations() { void mongoSessionStoreWithCustomizations() {
this.contextRunner this.contextRunner
.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, .withPropertyValues("spring.session.mongodb.collection-name=foo",
MongoReactiveAutoConfiguration.class, MongoReactiveDataAutoConfiguration.class))
.withPropertyValues("spring.session.store-type=mongodb", "spring.session.mongodb.collection-name=foo",
"spring.data.mongodb.uri=" + mongoDb.getReplicaSetUrl()) "spring.data.mongodb.uri=" + mongoDb.getReplicaSetUrl())
.run(validateSpringSessionUsesMongo("foo")); .run(validateSpringSessionUsesMongo("foo"));
} }
@Test @Test
void sessionCookieConfigurationIsAppliedToAutoConfiguredWebSessionIdResolver() { void sessionCookieConfigurationIsAppliedToAutoConfiguredWebSessionIdResolver() {
AutoConfigurations autoConfigurations = AutoConfigurations.of(MongoAutoConfiguration.class, AutoConfigurations autoConfigurations = AutoConfigurations.of(SessionAutoConfiguration.class,
MongoDataAutoConfiguration.class, MongoReactiveAutoConfiguration.class, MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, MongoReactiveAutoConfiguration.class,
MongoReactiveDataAutoConfiguration.class, WebSessionIdResolverAutoConfiguration.class); MongoReactiveDataAutoConfiguration.class, WebSessionIdResolverAutoConfiguration.class);
this.contextRunner.withConfiguration(autoConfigurations).withUserConfiguration(Config.class).withPropertyValues( new ReactiveWebApplicationContextRunner().withConfiguration(autoConfigurations)
"spring.session.store-type=mongodb", "server.reactive.session.cookie.name:JSESSIONID", .withUserConfiguration(Config.class)
"server.reactive.session.cookie.domain:.example.com", "server.reactive.session.cookie.path:/example", .withClassLoader(new FilteredClassLoader(ReactiveRedisSessionRepository.class))
"server.reactive.session.cookie.max-age:60", "server.reactive.session.cookie.http-only:false", .withPropertyValues("server.reactive.session.cookie.name:JSESSIONID",
"server.reactive.session.cookie.secure:false", "server.reactive.session.cookie.same-site:strict", "server.reactive.session.cookie.domain:.example.com",
"spring.data.mongodb.uri=" + mongoDb.getReplicaSetUrl()).run(assertExchangeWithSession((exchange) -> { "server.reactive.session.cookie.path:/example", "server.reactive.session.cookie.max-age:60",
"server.reactive.session.cookie.http-only:false", "server.reactive.session.cookie.secure:false",
"server.reactive.session.cookie.same-site:strict",
"spring.data.mongodb.uri=" + mongoDb.getReplicaSetUrl())
.run(assertExchangeWithSession((exchange) -> {
List<ResponseCookie> cookies = exchange.getResponse().getCookies().get("JSESSIONID"); List<ResponseCookie> cookies = exchange.getResponse().getCookies().get("JSESSIONID");
assertThat(cookies).isNotEmpty(); assertThat(cookies).isNotEmpty();
assertThat(cookies).allMatch((cookie) -> cookie.getDomain().equals(".example.com")); assertThat(cookies).allMatch((cookie) -> cookie.getDomain().equals(".example.com"));

@ -56,73 +56,57 @@ class ReactiveSessionAutoConfigurationRedisTests extends AbstractSessionAutoConf
.withStartupTimeout(Duration.ofMinutes(10)); .withStartupTimeout(Duration.ofMinutes(10));
protected final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() protected final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
.withConfiguration( .withClassLoader(new FilteredClassLoader(ReactiveMongoSessionRepository.class)).withConfiguration(
AutoConfigurations.of(SessionAutoConfiguration.class, WebSessionIdResolverAutoConfiguration.class)); AutoConfigurations.of(SessionAutoConfiguration.class, WebSessionIdResolverAutoConfiguration.class,
RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class));
@Test @Test
void defaultConfig() { void defaultConfig() {
this.contextRunner.withPropertyValues("spring.session.store-type=redis") this.contextRunner.run(validateSpringSessionUsesRedis("spring:session:", SaveMode.ON_SET_ATTRIBUTE));
.withConfiguration(
AutoConfigurations.of(RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class))
.run(validateSpringSessionUsesRedis("spring:session:", SaveMode.ON_SET_ATTRIBUTE));
} }
@Test @Test
void defaultConfigWithUniqueStoreImplementation() { void redisTakesPrecedenceMultipleImplementations() {
this.contextRunner.withClassLoader(new FilteredClassLoader(ReactiveMongoSessionRepository.class)) ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner().withConfiguration(
.withConfiguration( AutoConfigurations.of(SessionAutoConfiguration.class, WebSessionIdResolverAutoConfiguration.class,
AutoConfigurations.of(RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class)) RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class));
.run(validateSpringSessionUsesRedis("spring:session:", SaveMode.ON_SET_ATTRIBUTE)); contextRunner.run(validateSpringSessionUsesRedis("spring:session:", SaveMode.ON_SET_ATTRIBUTE));
} }
@Test @Test
void defaultConfigWithCustomTimeout() { void defaultConfigWithCustomTimeout() {
this.contextRunner.withPropertyValues("spring.session.store-type=redis", "spring.session.timeout=1m") this.contextRunner.withPropertyValues("spring.session.timeout=1m").run((context) -> {
.withConfiguration( ReactiveRedisSessionRepository repository = validateSessionRepository(context,
AutoConfigurations.of(RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class)) ReactiveRedisSessionRepository.class);
.run((context) -> { assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", 60);
ReactiveRedisSessionRepository repository = validateSessionRepository(context, });
ReactiveRedisSessionRepository.class);
assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", 60);
});
} }
@Test @Test
void defaultConfigWithCustomWebFluxTimeout() { void defaultConfigWithCustomWebFluxTimeout() {
this.contextRunner.withPropertyValues("spring.session.store-type=redis", "server.reactive.session.timeout=1m") this.contextRunner.withPropertyValues("server.reactive.session.timeout=1m").run((context) -> {
.withConfiguration( ReactiveRedisSessionRepository repository = validateSessionRepository(context,
AutoConfigurations.of(RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class)) ReactiveRedisSessionRepository.class);
.run((context) -> { assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", 60);
ReactiveRedisSessionRepository repository = validateSessionRepository(context, });
ReactiveRedisSessionRepository.class);
assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", 60);
});
} }
@Test @Test
void redisSessionStoreWithCustomizations() { void redisSessionStoreWithCustomizations() {
this.contextRunner this.contextRunner
.withConfiguration( .withPropertyValues("spring.session.redis.namespace=foo",
AutoConfigurations.of(RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class))
.withPropertyValues("spring.session.store-type=redis", "spring.session.redis.namespace=foo",
"spring.session.redis.save-mode=on-get-attribute") "spring.session.redis.save-mode=on-get-attribute")
.run(validateSpringSessionUsesRedis("foo:", SaveMode.ON_GET_ATTRIBUTE)); .run(validateSpringSessionUsesRedis("foo:", SaveMode.ON_GET_ATTRIBUTE));
} }
@Test @Test
void sessionCookieConfigurationIsAppliedToAutoConfiguredWebSessionIdResolver() { void sessionCookieConfigurationIsAppliedToAutoConfiguredWebSessionIdResolver() {
this.contextRunner this.contextRunner.withUserConfiguration(Config.class).withPropertyValues(
.withConfiguration( "spring.redis.host=" + redis.getHost(), "spring.redis.port=" + redis.getFirstMappedPort(),
AutoConfigurations.of(RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class)) "server.reactive.session.cookie.name:JSESSIONID", "server.reactive.session.cookie.domain:.example.com",
.withUserConfiguration(Config.class) "server.reactive.session.cookie.path:/example", "server.reactive.session.cookie.max-age:60",
.withPropertyValues("spring.session.store-type=redis", "spring.redis.host=" + redis.getHost(), "server.reactive.session.cookie.http-only:false", "server.reactive.session.cookie.secure:false",
"spring.redis.port=" + redis.getFirstMappedPort(), "spring.session.store-type=redis", "server.reactive.session.cookie.same-site:strict").run(assertExchangeWithSession((exchange) -> {
"server.reactive.session.cookie.name:JSESSIONID",
"server.reactive.session.cookie.domain:.example.com",
"server.reactive.session.cookie.path:/example", "server.reactive.session.cookie.max-age:60",
"server.reactive.session.cookie.http-only:false", "server.reactive.session.cookie.secure:false",
"server.reactive.session.cookie.same-site:strict")
.run(assertExchangeWithSession((exchange) -> {
List<ResponseCookie> cookies = exchange.getResponse().getCookies().get("JSESSIONID"); List<ResponseCookie> cookies = exchange.getResponse().getCookies().get("JSESSIONID");
assertThat(cookies).isNotEmpty(); assertThat(cookies).isNotEmpty();
assertThat(cookies).allMatch((cookie) -> cookie.getDomain().equals(".example.com")); assertThat(cookies).allMatch((cookie) -> cookie.getDomain().equals(".example.com"));

@ -47,30 +47,30 @@ import static org.mockito.Mockito.mock;
class SessionAutoConfigurationHazelcastTests extends AbstractSessionAutoConfigurationTests { class SessionAutoConfigurationHazelcastTests extends AbstractSessionAutoConfigurationTests {
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withClassLoader(new FilteredClassLoader(JdbcIndexedSessionRepository.class,
RedisIndexedSessionRepository.class, MongoIndexedSessionRepository.class))
.withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class)) .withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class))
.withUserConfiguration(HazelcastConfiguration.class); .withUserConfiguration(HazelcastConfiguration.class);
@Test @Test
void defaultConfig() { void defaultConfig() {
this.contextRunner.withPropertyValues("spring.session.store-type=hazelcast").run(this::validateDefaultConfig); this.contextRunner.run(this::validateDefaultConfig);
} }
@Test @Test
void defaultConfigWithUniqueStoreImplementation() { void hazelcastTakesPrecedenceOverJdbc() {
this.contextRunner this.contextRunner.withClassLoader(
.withClassLoader(new FilteredClassLoader(JdbcIndexedSessionRepository.class, new FilteredClassLoader(RedisIndexedSessionRepository.class, MongoIndexedSessionRepository.class))
RedisIndexedSessionRepository.class, MongoIndexedSessionRepository.class))
.run(this::validateDefaultConfig); .run(this::validateDefaultConfig);
} }
@Test @Test
void defaultConfigWithCustomTimeout() { void defaultConfigWithCustomTimeout() {
this.contextRunner.withPropertyValues("spring.session.store-type=hazelcast", "spring.session.timeout=1m") this.contextRunner.withPropertyValues("spring.session.timeout=1m").run((context) -> {
.run((context) -> { HazelcastIndexedSessionRepository repository = validateSessionRepository(context,
HazelcastIndexedSessionRepository repository = validateSessionRepository(context, HazelcastIndexedSessionRepository.class);
HazelcastIndexedSessionRepository.class); assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", 60);
assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", 60); });
});
} }
private void validateDefaultConfig(AssertableWebApplicationContext context) { private void validateDefaultConfig(AssertableWebApplicationContext context) {
@ -84,32 +84,29 @@ class SessionAutoConfigurationHazelcastTests extends AbstractSessionAutoConfigur
@Test @Test
void customMapName() { void customMapName() {
this.contextRunner.withPropertyValues("spring.session.store-type=hazelcast", this.contextRunner.withPropertyValues("spring.session.hazelcast.map-name=foo:bar:biz").run((context) -> {
"spring.session.hazelcast.map-name=foo:bar:biz").run((context) -> { validateSessionRepository(context, HazelcastIndexedSessionRepository.class);
validateSessionRepository(context, HazelcastIndexedSessionRepository.class); HazelcastInstance hazelcastInstance = context.getBean(HazelcastInstance.class);
HazelcastInstance hazelcastInstance = context.getBean(HazelcastInstance.class); then(hazelcastInstance).should().getMap("foo:bar:biz");
then(hazelcastInstance).should().getMap("foo:bar:biz"); });
});
} }
@Test @Test
void customFlushMode() { void customFlushMode() {
this.contextRunner.withPropertyValues("spring.session.store-type=hazelcast", this.contextRunner.withPropertyValues("spring.session.hazelcast.flush-mode=immediate").run((context) -> {
"spring.session.hazelcast.flush-mode=immediate").run((context) -> { HazelcastIndexedSessionRepository repository = validateSessionRepository(context,
HazelcastIndexedSessionRepository repository = validateSessionRepository(context, HazelcastIndexedSessionRepository.class);
HazelcastIndexedSessionRepository.class); assertThat(repository).hasFieldOrPropertyWithValue("flushMode", FlushMode.IMMEDIATE);
assertThat(repository).hasFieldOrPropertyWithValue("flushMode", FlushMode.IMMEDIATE); });
});
} }
@Test @Test
void customSaveMode() { void customSaveMode() {
this.contextRunner.withPropertyValues("spring.session.store-type=hazelcast", this.contextRunner.withPropertyValues("spring.session.hazelcast.save-mode=on-get-attribute").run((context) -> {
"spring.session.hazelcast.save-mode=on-get-attribute").run((context) -> { HazelcastIndexedSessionRepository repository = validateSessionRepository(context,
HazelcastIndexedSessionRepository repository = validateSessionRepository(context, HazelcastIndexedSessionRepository.class);
HazelcastIndexedSessionRepository.class); assertThat(repository).hasFieldOrPropertyWithValue("saveMode", SaveMode.ON_GET_ATTRIBUTE);
assertThat(repository).hasFieldOrPropertyWithValue("saveMode", SaveMode.ON_GET_ATTRIBUTE); });
});
} }
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)

@ -1,91 +0,0 @@
/*
* Copyright 2012-2022 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.boot.autoconfigure.session;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.map.IMap;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.jdbc.JdbcIndexedSessionRepository;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Integration tests for {@link SessionAutoConfiguration}.
*
* @author Stephane Nicoll
*/
class SessionAutoConfigurationIntegrationTests extends AbstractSessionAutoConfigurationTests {
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class, SessionAutoConfiguration.class))
.withPropertyValues("spring.datasource.generate-unique-name=true");
@Test
void severalCandidatesWithNoSessionStore() {
this.contextRunner.withUserConfiguration(HazelcastConfiguration.class).run((context) -> {
assertThat(context).hasFailed();
assertThat(context).getFailure().rootCause().isInstanceOf(NonUniqueSessionRepositoryException.class)
.hasMessageContaining("Multiple session repository candidates are available")
.hasMessageContaining("set the 'spring.session.store-type' property accordingly");
});
}
@Test
void severalCandidatesWithWrongSessionStore() {
this.contextRunner.withUserConfiguration(HazelcastConfiguration.class)
.withPropertyValues("spring.session.store-type=redis").run((context) -> {
assertThat(context).hasFailed();
assertThat(context).getFailure().hasCauseInstanceOf(SessionRepositoryUnavailableException.class);
assertThat(context).getFailure()
.hasMessageContaining("No session repository could be auto-configured");
assertThat(context).getFailure().hasMessageContaining("session store type is 'redis'");
});
}
@Test
void severalCandidatesWithValidSessionStore() {
this.contextRunner.withUserConfiguration(HazelcastConfiguration.class)
.withPropertyValues("spring.session.store-type=jdbc")
.run((context) -> validateSessionRepository(context, JdbcIndexedSessionRepository.class));
}
@Configuration(proxyBeanMethods = false)
static class HazelcastConfiguration {
@Bean
@SuppressWarnings("unchecked")
HazelcastInstance hazelcastInstance() {
IMap<Object, Object> map = mock(IMap.class);
HazelcastInstance mock = mock(HazelcastInstance.class);
given(mock.getMap("spring:session:sessions")).willReturn(map);
given(mock.getMap("foo:bar:biz")).willReturn(map);
return mock;
}
}
}

@ -62,6 +62,8 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
class SessionAutoConfigurationJdbcTests extends AbstractSessionAutoConfigurationTests { class SessionAutoConfigurationJdbcTests extends AbstractSessionAutoConfigurationTests {
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withClassLoader(new FilteredClassLoader(HazelcastIndexedSessionRepository.class,
MongoIndexedSessionRepository.class, RedisIndexedSessionRepository.class))
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class, JdbcTemplateAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, JdbcTemplateAutoConfiguration.class,
SessionAutoConfiguration.class)) SessionAutoConfiguration.class))
@ -69,15 +71,7 @@ class SessionAutoConfigurationJdbcTests extends AbstractSessionAutoConfiguration
@Test @Test
void defaultConfig() { void defaultConfig() {
this.contextRunner.withPropertyValues("spring.session.store-type=jdbc").run(this::validateDefaultConfig); this.contextRunner.run(this::validateDefaultConfig);
}
@Test
void defaultConfigWithUniqueStoreImplementation() {
this.contextRunner
.withClassLoader(new FilteredClassLoader(HazelcastIndexedSessionRepository.class,
MongoIndexedSessionRepository.class, RedisIndexedSessionRepository.class))
.run(this::validateDefaultConfig);
} }
private void validateDefaultConfig(AssertableWebApplicationContext context) { private void validateDefaultConfig(AssertableWebApplicationContext context) {
@ -96,46 +90,39 @@ class SessionAutoConfigurationJdbcTests extends AbstractSessionAutoConfiguration
@Test @Test
void filterOrderCanBeCustomized() { void filterOrderCanBeCustomized() {
this.contextRunner this.contextRunner.withPropertyValues("spring.session.servlet.filter-order=123").run((context) -> {
.withPropertyValues("spring.session.store-type=jdbc", "spring.session.servlet.filter-order=123") FilterRegistrationBean<?> registration = context.getBean(FilterRegistrationBean.class);
.run((context) -> { assertThat(registration.getOrder()).isEqualTo(123);
FilterRegistrationBean<?> registration = context.getBean(FilterRegistrationBean.class); });
assertThat(registration.getOrder()).isEqualTo(123);
});
} }
@Test @Test
void disableDataSourceInitializer() { void disableDataSourceInitializer() {
this.contextRunner this.contextRunner.withPropertyValues("spring.session.jdbc.initialize-schema=never").run((context) -> {
.withPropertyValues("spring.session.store-type=jdbc", "spring.session.jdbc.initialize-schema=never") assertThat(context).doesNotHaveBean(JdbcSessionDataSourceScriptDatabaseInitializer.class);
.run((context) -> { JdbcIndexedSessionRepository repository = validateSessionRepository(context,
assertThat(context).doesNotHaveBean(JdbcSessionDataSourceScriptDatabaseInitializer.class); JdbcIndexedSessionRepository.class);
JdbcIndexedSessionRepository repository = validateSessionRepository(context, assertThat(repository).hasFieldOrPropertyWithValue("tableName", "SPRING_SESSION");
JdbcIndexedSessionRepository.class); assertThat(context.getBean(JdbcSessionProperties.class).getInitializeSchema())
assertThat(repository).hasFieldOrPropertyWithValue("tableName", "SPRING_SESSION"); .isEqualTo(DatabaseInitializationMode.NEVER);
assertThat(context.getBean(JdbcSessionProperties.class).getInitializeSchema()) assertThatExceptionOfType(BadSqlGrammarException.class).isThrownBy(
.isEqualTo(DatabaseInitializationMode.NEVER); () -> context.getBean(JdbcOperations.class).queryForList("select * from SPRING_SESSION"));
assertThatExceptionOfType(BadSqlGrammarException.class).isThrownBy( });
() -> context.getBean(JdbcOperations.class).queryForList("select * from SPRING_SESSION"));
});
} }
@Test @Test
void customTimeout() { void customTimeout() {
this.contextRunner.withPropertyValues("spring.session.store-type=jdbc", "spring.session.timeout=1m") this.contextRunner.withPropertyValues("spring.session.timeout=1m").run((context) -> {
.run((context) -> { JdbcIndexedSessionRepository repository = validateSessionRepository(context,
JdbcIndexedSessionRepository repository = validateSessionRepository(context, JdbcIndexedSessionRepository.class);
JdbcIndexedSessionRepository.class); assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", 60);
assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", 60); });
});
} }
@Test @Test
void customTableName() { void customTableName() {
this.contextRunner this.contextRunner.withPropertyValues("spring.session.jdbc.table-name=FOO_BAR",
.withPropertyValues("spring.session.store-type=jdbc", "spring.session.jdbc.table-name=FOO_BAR", "spring.session.jdbc.schema=classpath:session/custom-schema-h2.sql").run((context) -> {
"spring.session.jdbc.schema=classpath:session/custom-schema-h2.sql")
.run((context) -> {
JdbcIndexedSessionRepository repository = validateSessionRepository(context, JdbcIndexedSessionRepository repository = validateSessionRepository(context,
JdbcIndexedSessionRepository.class); JdbcIndexedSessionRepository.class);
assertThat(repository).hasFieldOrPropertyWithValue("tableName", "FOO_BAR"); assertThat(repository).hasFieldOrPropertyWithValue("tableName", "FOO_BAR");
@ -147,60 +134,51 @@ class SessionAutoConfigurationJdbcTests extends AbstractSessionAutoConfiguration
@Test @Test
void customCleanupCron() { void customCleanupCron() {
this.contextRunner this.contextRunner.withPropertyValues("spring.session.jdbc.cleanup-cron=0 0 12 * * *").run((context) -> {
.withPropertyValues("spring.session.store-type=jdbc", "spring.session.jdbc.cleanup-cron=0 0 12 * * *") assertThat(context.getBean(JdbcSessionProperties.class).getCleanupCron()).isEqualTo("0 0 12 * * *");
.run((context) -> { SpringBootJdbcHttpSessionConfiguration configuration = context
assertThat(context.getBean(JdbcSessionProperties.class).getCleanupCron()).isEqualTo("0 0 12 * * *"); .getBean(SpringBootJdbcHttpSessionConfiguration.class);
SpringBootJdbcHttpSessionConfiguration configuration = context assertThat(configuration).hasFieldOrPropertyWithValue("cleanupCron", "0 0 12 * * *");
.getBean(SpringBootJdbcHttpSessionConfiguration.class); });
assertThat(configuration).hasFieldOrPropertyWithValue("cleanupCron", "0 0 12 * * *");
});
} }
@Test @Test
void customFlushMode() { void customFlushMode() {
this.contextRunner this.contextRunner.withPropertyValues("spring.session.jdbc.flush-mode=immediate").run((context) -> {
.withPropertyValues("spring.session.store-type=jdbc", "spring.session.jdbc.flush-mode=immediate") assertThat(context.getBean(JdbcSessionProperties.class).getFlushMode()).isEqualTo(FlushMode.IMMEDIATE);
.run((context) -> { SpringBootJdbcHttpSessionConfiguration configuration = context
assertThat(context.getBean(JdbcSessionProperties.class).getFlushMode()) .getBean(SpringBootJdbcHttpSessionConfiguration.class);
.isEqualTo(FlushMode.IMMEDIATE); assertThat(configuration).hasFieldOrPropertyWithValue("flushMode", FlushMode.IMMEDIATE);
SpringBootJdbcHttpSessionConfiguration configuration = context });
.getBean(SpringBootJdbcHttpSessionConfiguration.class);
assertThat(configuration).hasFieldOrPropertyWithValue("flushMode", FlushMode.IMMEDIATE);
});
} }
@Test @Test
void customSaveMode() { void customSaveMode() {
this.contextRunner this.contextRunner.withPropertyValues("spring.session.jdbc.save-mode=on-get-attribute").run((context) -> {
.withPropertyValues("spring.session.store-type=jdbc", "spring.session.jdbc.save-mode=on-get-attribute") assertThat(context.getBean(JdbcSessionProperties.class).getSaveMode()).isEqualTo(SaveMode.ON_GET_ATTRIBUTE);
.run((context) -> { SpringBootJdbcHttpSessionConfiguration configuration = context
assertThat(context.getBean(JdbcSessionProperties.class).getSaveMode()) .getBean(SpringBootJdbcHttpSessionConfiguration.class);
.isEqualTo(SaveMode.ON_GET_ATTRIBUTE); assertThat(configuration).hasFieldOrPropertyWithValue("saveMode", SaveMode.ON_GET_ATTRIBUTE);
SpringBootJdbcHttpSessionConfiguration configuration = context });
.getBean(SpringBootJdbcHttpSessionConfiguration.class);
assertThat(configuration).hasFieldOrPropertyWithValue("saveMode", SaveMode.ON_GET_ATTRIBUTE);
});
} }
@Test @Test
void sessionDataSourceIsUsedWhenAvailable() { void sessionDataSourceIsUsedWhenAvailable() {
this.contextRunner.withUserConfiguration(SessionDataSourceConfiguration.class) this.contextRunner.withUserConfiguration(SessionDataSourceConfiguration.class).run((context) -> {
.withPropertyValues("spring.session.store-type=jdbc").run((context) -> { JdbcIndexedSessionRepository repository = validateSessionRepository(context,
JdbcIndexedSessionRepository repository = validateSessionRepository(context, JdbcIndexedSessionRepository.class);
JdbcIndexedSessionRepository.class); DataSource sessionDataSource = context.getBean("sessionDataSource", DataSource.class);
DataSource sessionDataSource = context.getBean("sessionDataSource", DataSource.class); assertThat(repository).extracting("jdbcOperations.dataSource").isEqualTo(sessionDataSource);
assertThat(repository).extracting("jdbcOperations.dataSource").isEqualTo(sessionDataSource); assertThat(context.getBean(JdbcSessionDataSourceScriptDatabaseInitializer.class))
assertThat(context.getBean(JdbcSessionDataSourceScriptDatabaseInitializer.class)) .hasFieldOrPropertyWithValue("dataSource", sessionDataSource);
.hasFieldOrPropertyWithValue("dataSource", sessionDataSource); assertThatExceptionOfType(BadSqlGrammarException.class).isThrownBy(
assertThatExceptionOfType(BadSqlGrammarException.class).isThrownBy( () -> context.getBean(JdbcOperations.class).queryForList("select * from SPRING_SESSION"));
() -> context.getBean(JdbcOperations.class).queryForList("select * from SPRING_SESSION")); });
});
} }
@Test @Test
void sessionRepositoryBeansDependOnJdbcSessionDataSourceInitializer() { void sessionRepositoryBeansDependOnJdbcSessionDataSourceInitializer() {
this.contextRunner.withPropertyValues("spring.session.store-type=jdbc").run((context) -> { this.contextRunner.run((context) -> {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
String[] sessionRepositoryNames = beanFactory.getBeanNamesForType(JdbcIndexedSessionRepository.class); String[] sessionRepositoryNames = beanFactory.getBeanNamesForType(JdbcIndexedSessionRepository.class);
assertThat(sessionRepositoryNames).isNotEmpty(); assertThat(sessionRepositoryNames).isNotEmpty();
@ -214,8 +192,7 @@ class SessionAutoConfigurationJdbcTests extends AbstractSessionAutoConfiguration
@Test @Test
void sessionRepositoryBeansDependOnFlyway() { void sessionRepositoryBeansDependOnFlyway() {
this.contextRunner.withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class)) this.contextRunner.withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class))
.withPropertyValues("spring.session.store-type=jdbc", "spring.session.jdbc.initialize-schema=never") .withPropertyValues("spring.session.jdbc.initialize-schema=never").run((context) -> {
.run((context) -> {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
String[] sessionRepositoryNames = beanFactory String[] sessionRepositoryNames = beanFactory
.getBeanNamesForType(JdbcIndexedSessionRepository.class); .getBeanNamesForType(JdbcIndexedSessionRepository.class);
@ -230,8 +207,7 @@ class SessionAutoConfigurationJdbcTests extends AbstractSessionAutoConfiguration
@Test @Test
void sessionRepositoryBeansDependOnLiquibase() { void sessionRepositoryBeansDependOnLiquibase() {
this.contextRunner.withConfiguration(AutoConfigurations.of(LiquibaseAutoConfiguration.class)) this.contextRunner.withConfiguration(AutoConfigurations.of(LiquibaseAutoConfiguration.class))
.withPropertyValues("spring.session.store-type=jdbc", "spring.session.jdbc.initialize-schema=never") .withPropertyValues("spring.session.jdbc.initialize-schema=never").run((context) -> {
.run((context) -> {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
String[] sessionRepositoryNames = beanFactory String[] sessionRepositoryNames = beanFactory
.getBeanNamesForType(JdbcIndexedSessionRepository.class); .getBeanNamesForType(JdbcIndexedSessionRepository.class);
@ -248,7 +224,6 @@ class SessionAutoConfigurationJdbcTests extends AbstractSessionAutoConfiguration
this.contextRunner.withUserConfiguration(CustomJdbcSessionDatabaseInitializerConfiguration.class) this.contextRunner.withUserConfiguration(CustomJdbcSessionDatabaseInitializerConfiguration.class)
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class)) DataSourceTransactionManagerAutoConfiguration.class))
.withPropertyValues("spring.session.store-type=jdbc")
.run((context) -> assertThat(context) .run((context) -> assertThat(context)
.hasSingleBean(JdbcSessionDataSourceScriptDatabaseInitializer.class) .hasSingleBean(JdbcSessionDataSourceScriptDatabaseInitializer.class)
.doesNotHaveBean("jdbcSessionDataSourceScriptDatabaseInitializer") .doesNotHaveBean("jdbcSessionDataSourceScriptDatabaseInitializer")
@ -260,7 +235,6 @@ class SessionAutoConfigurationJdbcTests extends AbstractSessionAutoConfiguration
this.contextRunner.withUserConfiguration(CustomDatabaseInitializerConfiguration.class) this.contextRunner.withUserConfiguration(CustomDatabaseInitializerConfiguration.class)
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class)) DataSourceTransactionManagerAutoConfiguration.class))
.withPropertyValues("spring.session.store-type=jdbc")
.run((context) -> assertThat(context) .run((context) -> assertThat(context)
.hasSingleBean(JdbcSessionDataSourceScriptDatabaseInitializer.class) .hasSingleBean(JdbcSessionDataSourceScriptDatabaseInitializer.class)
.hasBean("customInitializer")); .hasBean("customInitializer"));

@ -52,34 +52,32 @@ class SessionAutoConfigurationMongoTests extends AbstractSessionAutoConfiguratio
.withStartupTimeout(Duration.ofMinutes(5)); .withStartupTimeout(Duration.ofMinutes(5));
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withClassLoader(new FilteredClassLoader(HazelcastIndexedSessionRepository.class,
JdbcIndexedSessionRepository.class, RedisIndexedSessionRepository.class))
.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class,
SessionAutoConfiguration.class)) SessionAutoConfiguration.class))
.withPropertyValues("spring.data.mongodb.uri=" + mongoDB.getReplicaSetUrl()); .withPropertyValues("spring.data.mongodb.uri=" + mongoDB.getReplicaSetUrl());
@Test @Test
void defaultConfig() { void defaultConfig() {
this.contextRunner.withPropertyValues("spring.session.store-type=mongodb") this.contextRunner.run(validateSpringSessionUsesMongo("sessions"));
.run(validateSpringSessionUsesMongo("sessions"));
} }
@Test @Test
void defaultConfigWithUniqueStoreImplementation() { void mongoTakesPrecedenceOverJdbcAndHazelcast() {
this.contextRunner this.contextRunner.withClassLoader(new FilteredClassLoader(RedisIndexedSessionRepository.class))
.withClassLoader(new FilteredClassLoader(HazelcastIndexedSessionRepository.class,
JdbcIndexedSessionRepository.class, RedisIndexedSessionRepository.class))
.run(validateSpringSessionUsesMongo("sessions")); .run(validateSpringSessionUsesMongo("sessions"));
} }
@Test @Test
void defaultConfigWithCustomTimeout() { void defaultConfigWithCustomTimeout() {
this.contextRunner.withPropertyValues("spring.session.store-type=mongodb", "spring.session.timeout=1m") this.contextRunner.withPropertyValues("spring.session.timeout=1m")
.run(validateSpringSessionUsesMongo("sessions", Duration.ofMinutes(1))); .run(validateSpringSessionUsesMongo("sessions", Duration.ofMinutes(1)));
} }
@Test @Test
void mongoSessionStoreWithCustomizations() { void mongoSessionStoreWithCustomizations() {
this.contextRunner this.contextRunner.withPropertyValues("spring.session.mongodb.collection-name=foo")
.withPropertyValues("spring.session.store-type=mongodb", "spring.session.mongodb.collection-name=foo")
.run(validateSpringSessionUsesMongo("foo")); .run(validateSpringSessionUsesMongo("foo"));
} }

@ -60,12 +60,14 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio
.withStartupTimeout(Duration.ofMinutes(10)); .withStartupTimeout(Duration.ofMinutes(10));
protected final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() protected final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withClassLoader(new FilteredClassLoader(HazelcastIndexedSessionRepository.class,
JdbcIndexedSessionRepository.class, MongoIndexedSessionRepository.class))
.withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class)); .withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class));
@Test @Test
void defaultConfig() { void defaultConfig() {
this.contextRunner this.contextRunner
.withPropertyValues("spring.session.store-type=redis", "spring.redis.host=" + redis.getHost(), .withPropertyValues("spring.redis.host=" + redis.getHost(),
"spring.redis.port=" + redis.getFirstMappedPort()) "spring.redis.port=" + redis.getFirstMappedPort())
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) .withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
.run(validateSpringSessionUsesRedis("spring:session:event:0:created:", FlushMode.ON_SAVE, .run(validateSpringSessionUsesRedis("spring:session:event:0:created:", FlushMode.ON_SAVE,
@ -73,11 +75,8 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio
} }
@Test @Test
void defaultConfigWithUniqueStoreImplementation() { void redisTakesPrecedenceMultipleImplementations() {
this.contextRunner this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
.withClassLoader(new FilteredClassLoader(HazelcastIndexedSessionRepository.class,
JdbcIndexedSessionRepository.class, MongoIndexedSessionRepository.class))
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
.withPropertyValues("spring.redis.host=" + redis.getHost(), .withPropertyValues("spring.redis.host=" + redis.getHost(),
"spring.redis.port=" + redis.getFirstMappedPort()) "spring.redis.port=" + redis.getFirstMappedPort())
.run(validateSpringSessionUsesRedis("spring:session:event:0:created:", FlushMode.ON_SAVE, .run(validateSpringSessionUsesRedis("spring:session:event:0:created:", FlushMode.ON_SAVE,
@ -87,7 +86,7 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio
@Test @Test
void defaultConfigWithCustomTimeout() { void defaultConfigWithCustomTimeout() {
this.contextRunner this.contextRunner
.withPropertyValues("spring.session.store-type=redis", "spring.redis.host=" + redis.getHost(), .withPropertyValues("spring.redis.host=" + redis.getHost(),
"spring.redis.port=" + redis.getFirstMappedPort(), "spring.session.timeout=1m") "spring.redis.port=" + redis.getFirstMappedPort(), "spring.session.timeout=1m")
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)).run((context) -> { .withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)).run((context) -> {
RedisIndexedSessionRepository repository = validateSessionRepository(context, RedisIndexedSessionRepository repository = validateSessionRepository(context,
@ -99,8 +98,8 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio
@Test @Test
void redisSessionStoreWithCustomizations() { void redisSessionStoreWithCustomizations() {
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
.withPropertyValues("spring.session.store-type=redis", "spring.session.redis.namespace=foo", .withPropertyValues("spring.session.redis.namespace=foo", "spring.session.redis.flush-mode=immediate",
"spring.session.redis.flush-mode=immediate", "spring.session.redis.save-mode=on-get-attribute", "spring.session.redis.save-mode=on-get-attribute",
"spring.session.redis.cleanup-cron=0 0 12 * * *", "spring.redis.host=" + redis.getHost(), "spring.session.redis.cleanup-cron=0 0 12 * * *", "spring.redis.host=" + redis.getHost(),
"spring.redis.port=" + redis.getFirstMappedPort()) "spring.redis.port=" + redis.getFirstMappedPort())
.run(validateSpringSessionUsesRedis("foo:event:0:created:", FlushMode.IMMEDIATE, .run(validateSpringSessionUsesRedis("foo:event:0:created:", FlushMode.IMMEDIATE,
@ -110,7 +109,7 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio
@Test @Test
void redisSessionWithConfigureActionNone() { void redisSessionWithConfigureActionNone() {
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
.withPropertyValues("spring.session.store-type=redis", "spring.session.redis.configure-action=none", .withPropertyValues("spring.session.redis.configure-action=none",
"spring.redis.host=" + redis.getHost(), "spring.redis.port=" + redis.getFirstMappedPort()) "spring.redis.host=" + redis.getHost(), "spring.redis.port=" + redis.getFirstMappedPort())
.run(validateStrategy(ConfigureRedisAction.NO_OP.getClass())); .run(validateStrategy(ConfigureRedisAction.NO_OP.getClass()));
} }
@ -118,7 +117,7 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio
@Test @Test
void redisSessionWithDefaultConfigureActionNone() { void redisSessionWithDefaultConfigureActionNone() {
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
.withPropertyValues("spring.session.store-type=redis", "spring.redis.host=" + redis.getHost(), .withPropertyValues("spring.redis.host=" + redis.getHost(),
"spring.redis.port=" + redis.getFirstMappedPort()) "spring.redis.port=" + redis.getFirstMappedPort())
.run(validateStrategy(ConfigureNotifyKeyspaceEventsAction.class, .run(validateStrategy(ConfigureNotifyKeyspaceEventsAction.class,
entry("notify-keyspace-events", "gxE"))); entry("notify-keyspace-events", "gxE")));
@ -128,7 +127,7 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio
void redisSessionWithCustomConfigureRedisActionBean() { void redisSessionWithCustomConfigureRedisActionBean() {
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
.withUserConfiguration(MaxEntriesRedisAction.class) .withUserConfiguration(MaxEntriesRedisAction.class)
.withPropertyValues("spring.session.store-type=redis", "spring.redis.host=" + redis.getHost(), .withPropertyValues("spring.redis.host=" + redis.getHost(),
"spring.redis.port=" + redis.getFirstMappedPort()) "spring.redis.port=" + redis.getFirstMappedPort())
.run(validateStrategy(MaxEntriesRedisAction.class, entry("set-max-intset-entries", "1024"))); .run(validateStrategy(MaxEntriesRedisAction.class, entry("set-max-intset-entries", "1024")));

@ -26,14 +26,22 @@ import org.mockito.InOrder;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.session.MapSessionRepository; import org.springframework.session.MapSessionRepository;
import org.springframework.session.ReactiveMapSessionRepository;
import org.springframework.session.SessionRepository; import org.springframework.session.SessionRepository;
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession; import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
import org.springframework.session.config.annotation.web.server.EnableSpringWebSession;
import org.springframework.session.data.mongo.MongoIndexedSessionRepository;
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository;
import org.springframework.session.jdbc.JdbcIndexedSessionRepository;
import org.springframework.session.security.web.authentication.SpringSessionRememberMeServices; import org.springframework.session.security.web.authentication.SpringSessionRememberMeServices;
import org.springframework.session.web.http.CookieHttpSessionIdResolver; import org.springframework.session.web.http.CookieHttpSessionIdResolver;
import org.springframework.session.web.http.DefaultCookieSerializer; import org.springframework.session.web.http.DefaultCookieSerializer;
@ -60,37 +68,31 @@ class SessionAutoConfigurationTests extends AbstractSessionAutoConfigurationTest
.withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class)); .withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class));
@Test @Test
void contextFailsIfMultipleStoresAreAvailable() { void autoConfigurationDisabledIfNoImplementationMatches() {
this.contextRunner.run((context) -> { this.contextRunner
assertThat(context).hasFailed(); .withClassLoader(new FilteredClassLoader(RedisIndexedSessionRepository.class,
assertThat(context).getFailure().rootCause().isInstanceOf(NonUniqueSessionRepositoryException.class) HazelcastIndexedSessionRepository.class, JdbcIndexedSessionRepository.class,
.hasMessageContaining("Multiple session repository candidates are available"); MongoIndexedSessionRepository.class))
}); .run((context) -> assertThat(context).doesNotHaveBean(SessionRepository.class));
} }
@Test @Test
void contextFailsIfStoreTypeNotAvailable() { void backOffIfSessionRepositoryIsPresent() {
this.contextRunner.withPropertyValues("spring.session.store-type=jdbc").run((context) -> { this.contextRunner.withUserConfiguration(SessionRepositoryConfiguration.class).run((context) -> {
assertThat(context).hasFailed(); MapSessionRepository repository = validateSessionRepository(context, MapSessionRepository.class);
assertThat(context).getFailure().hasCauseInstanceOf(SessionRepositoryUnavailableException.class); assertThat(context).getBean("mySessionRepository").isSameAs(repository);
assertThat(context).getFailure().hasMessageContaining("No session repository could be auto-configured");
assertThat(context).getFailure().hasMessageContaining("session store type is 'jdbc'");
}); });
} }
@Test @Test
void autoConfigurationDisabledIfStoreTypeSetToNone() { void backOffIfReactiveSessionRepositoryIsPresent() {
this.contextRunner.withPropertyValues("spring.session.store-type=none") ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
.run((context) -> assertThat(context).doesNotHaveBean(SessionRepository.class)); .withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class));
} contextRunner.withUserConfiguration(ReactiveSessionRepositoryConfiguration.class).run((context) -> {
ReactiveMapSessionRepository repository = validateSessionRepository(context,
@Test ReactiveMapSessionRepository.class);
void backOffIfSessionRepositoryIsPresent() { assertThat(context).getBean("mySessionRepository").isSameAs(repository);
this.contextRunner.withUserConfiguration(SessionRepositoryConfiguration.class) });
.withPropertyValues("spring.session.store-type=redis").run((context) -> {
MapSessionRepository repository = validateSessionRepository(context, MapSessionRepository.class);
assertThat(context).getBean("mySessionRepository").isSameAs(repository);
});
} }
@Test @Test
@ -220,6 +222,17 @@ class SessionAutoConfigurationTests extends AbstractSessionAutoConfigurationTest
} }
@Configuration(proxyBeanMethods = false)
@EnableSpringWebSession
static class ReactiveSessionRepositoryConfiguration {
@Bean
ReactiveMapSessionRepository mySessionRepository() {
return new ReactiveMapSessionRepository(Collections.emptyMap());
}
}
@EnableConfigurationProperties(ServerProperties.class) @EnableConfigurationProperties(ServerProperties.class)
static class ServerPropertiesConfiguration { static class ServerPropertiesConfiguration {

@ -3,15 +3,25 @@
Spring Boot provides {spring-session}[Spring Session] auto-configuration for a wide range of data stores. Spring Boot provides {spring-session}[Spring Session] auto-configuration for a wide range of data stores.
When building a servlet web application, the following stores can be auto-configured: When building a servlet web application, the following stores can be auto-configured:
* JDBC
* Redis * Redis
* Hazelcast
* MongoDB * MongoDB
* Hazelcast
* JDBC
Additionally, {spring-boot-for-apache-geode}[Spring Boot for Apache Geode] provides {spring-boot-for-apache-geode-docs}#geode-session[auto-configuration for using Apache Geode as a session store]. Additionally, {spring-boot-for-apache-geode}[Spring Boot for Apache Geode] provides {spring-boot-for-apache-geode-docs}#geode-session[auto-configuration for using Apache Geode as a session store].
The servlet auto-configuration replaces the need to use `@Enable*HttpSession`. The servlet auto-configuration replaces the need to use `@Enable*HttpSession`.
If a single Spring Session module is present on the classpath, Spring Boot uses that store implementation automatically.
If you have more than one implementation, Spring Boot uses the following order for choosing a specific implementation:
. Redis
. MongoDB
. Hazelcast
. JDBC
. If none of Redis, MongoDB, Hazelcast and JDBC are available, we do not configure a `SessionRepository`.
When building a reactive web application, the following stores can be auto-configured: When building a reactive web application, the following stores can be auto-configured:
* Redis * Redis
@ -19,18 +29,12 @@ When building a reactive web application, the following stores can be auto-confi
The reactive auto-configuration replaces the need to use `@Enable*WebSession`. The reactive auto-configuration replaces the need to use `@Enable*WebSession`.
If a single Spring Session module is present on the classpath, Spring Boot uses that store implementation automatically. Similar to the servlet configuration, if you have more than one implementation, Spring Boot uses the following order for choosing a specific implementation:
If you have more than one implementation, you must choose the {spring-boot-autoconfigure-module-code}/session/StoreType.java[`StoreType`] that you wish to use to store the sessions.
For instance, to use JDBC as the back-end store, you can configure your application as follows:
[source,yaml,indent=0,subs="verbatim",configprops,configblocks] . Redis
---- . MongoDB
spring: . If neither Redis nor MongoDB are available, we do not configure a `ReactiveSessionRepository`.
session:
store-type: "jdbc"
----
TIP: You can disable Spring Session by setting the `store-type` to `none`.
Each store has specific additional settings. Each store has specific additional settings.
For instance, it is possible to customize the name of the table for the JDBC store, as shown in the following example: For instance, it is possible to customize the name of the table for the JDBC store, as shown in the following example:

Loading…
Cancel
Save