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

@ -50,7 +50,6 @@ import org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessi
@ConditionalOnClass({ JdbcTemplate.class, JdbcIndexedSessionRepository.class })
@ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnBean(DataSource.class)
@Conditional(ServletSessionCondition.class)
@EnableConfigurationProperties(JdbcSessionProperties.class)
@Import(DatabaseInitializationDependencyConfigurer.class)
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.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
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 })
@ConditionalOnMissingBean(ReactiveSessionRepository.class)
@ConditionalOnBean(ReactiveMongoOperations.class)
@Conditional(ReactiveSessionCondition.class)
@EnableConfigurationProperties(MongoSessionProperties.class)
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.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.MongoOperations;
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 })
@ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnBean(MongoOperations.class)
@Conditional(ServletSessionCondition.class)
@EnableConfigurationProperties(MongoSessionProperties.class)
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.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
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 })
@ConditionalOnMissingBean(ReactiveSessionRepository.class)
@ConditionalOnBean(ReactiveRedisConnectionFactory.class)
@Conditional(ReactiveSessionCondition.class)
@EnableConfigurationProperties(RedisSessionProperties.class)
class RedisReactiveSessionConfiguration {

@ -25,7 +25,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
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 })
@ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@Conditional(ServletSessionCondition.class)
@EnableConfigurationProperties(RedisSessionProperties.class)
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;
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.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
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.web.server.Cookie.SameSite;
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.Conditional;
import org.springframework.context.annotation.Configuration;
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.session.ReactiveSessionRepository;
import org.springframework.session.Session;
@ -90,7 +81,7 @@ public class SessionAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@Import({ ServletSessionRepositoryValidator.class, SessionRepositoryFilterConfiguration.class })
@Import(SessionRepositoryFilterConfiguration.class)
static class ServletSessionConfiguration {
@Bean
@ -125,8 +116,8 @@ public class SessionAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(SessionRepository.class)
@Import({ ServletSessionRepositoryImplementationValidator.class,
ServletSessionConfigurationImportSelector.class })
@Import({ RedisSessionConfiguration.class, MongoSessionConfiguration.class, HazelcastSessionConfiguration.class,
JdbcSessionConfiguration.class })
static class ServletSessionRepositoryConfiguration {
}
@ -135,17 +126,10 @@ public class SessionAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.REACTIVE)
@Import(ReactiveSessionRepositoryValidator.class)
@ConditionalOnMissingBean(ReactiveSessionRepository.class)
@Import({ RedisReactiveSessionConfiguration.class, MongoReactiveSessionConfiguration.class })
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")
public class SessionProperties {
/**
* Session store type.
*/
private StoreType storeType;
/**
* 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();
public StoreType getStoreType() {
return this.storeType;
}
public void setStoreType(StoreType storeType) {
this.storeType = storeType;
}
public Duration getTimeout() {
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.MissingR2dbcPoolDependencyFailureAnalyzer,\
org.springframework.boot.autoconfigure.r2dbc.MultipleConnectionPoolConfigurationsFailureAnalzyer,\
org.springframework.boot.autoconfigure.r2dbc.NoConnectionFactoryBeanFailureAnalyzer,\
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer
org.springframework.boot.autoconfigure.r2dbc.NoConnectionFactoryBeanFailureAnalyzer
# Template availability providers
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));
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
void defaultConfig() {
this.contextRunner
.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))
this.contextRunner.withPropertyValues("spring.data.mongodb.uri=" + mongoDb.getReplicaSetUrl())
.run(validateSpringSessionUsesMongo("sessions"));
}
@Test
void defaultConfigWithCustomTimeout() {
this.contextRunner
.withPropertyValues("spring.session.store-type=mongodb", "spring.session.timeout=1m",
"spring.data.mongodb.uri=" + mongoDb.getReplicaSetUrl())
.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class,
MongoReactiveAutoConfiguration.class, MongoReactiveDataAutoConfiguration.class))
.run((context) -> {
this.contextRunner.withPropertyValues("spring.session.timeout=1m",
"spring.data.mongodb.uri=" + mongoDb.getReplicaSetUrl()).run((context) -> {
ReactiveMongoSessionRepository repository = validateSessionRepository(context,
ReactiveMongoSessionRepository.class);
assertThat(repository).hasFieldOrPropertyWithValue("maxInactiveIntervalInSeconds", 60);
@ -92,12 +78,8 @@ class ReactiveSessionAutoConfigurationMongoTests extends AbstractSessionAutoConf
@Test
void defaultConfigWithCustomSessionTimeout() {
this.contextRunner
.withPropertyValues("spring.session.store-type=mongodb", "server.reactive.session.timeout=1m",
"spring.data.mongodb.uri=" + mongoDb.getReplicaSetUrl())
.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class,
MongoReactiveAutoConfiguration.class, MongoReactiveDataAutoConfiguration.class))
.run((context) -> {
this.contextRunner.withPropertyValues("server.reactive.session.timeout=1m",
"spring.data.mongodb.uri=" + mongoDb.getReplicaSetUrl()).run((context) -> {
ReactiveMongoSessionRepository repository = validateSessionRepository(context,
ReactiveMongoSessionRepository.class);
assertThat(repository).hasFieldOrPropertyWithValue("maxInactiveIntervalInSeconds", 60);
@ -107,24 +89,26 @@ class ReactiveSessionAutoConfigurationMongoTests extends AbstractSessionAutoConf
@Test
void mongoSessionStoreWithCustomizations() {
this.contextRunner
.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class,
MongoReactiveAutoConfiguration.class, MongoReactiveDataAutoConfiguration.class))
.withPropertyValues("spring.session.store-type=mongodb", "spring.session.mongodb.collection-name=foo",
.withPropertyValues("spring.session.mongodb.collection-name=foo",
"spring.data.mongodb.uri=" + mongoDb.getReplicaSetUrl())
.run(validateSpringSessionUsesMongo("foo"));
}
@Test
void sessionCookieConfigurationIsAppliedToAutoConfiguredWebSessionIdResolver() {
AutoConfigurations autoConfigurations = AutoConfigurations.of(MongoAutoConfiguration.class,
MongoDataAutoConfiguration.class, MongoReactiveAutoConfiguration.class,
AutoConfigurations autoConfigurations = AutoConfigurations.of(SessionAutoConfiguration.class,
MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, MongoReactiveAutoConfiguration.class,
MongoReactiveDataAutoConfiguration.class, WebSessionIdResolverAutoConfiguration.class);
this.contextRunner.withConfiguration(autoConfigurations).withUserConfiguration(Config.class).withPropertyValues(
"spring.session.store-type=mongodb", "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",
"spring.data.mongodb.uri=" + mongoDb.getReplicaSetUrl()).run(assertExchangeWithSession((exchange) -> {
new ReactiveWebApplicationContextRunner().withConfiguration(autoConfigurations)
.withUserConfiguration(Config.class)
.withClassLoader(new FilteredClassLoader(ReactiveRedisSessionRepository.class))
.withPropertyValues("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",
"spring.data.mongodb.uri=" + mongoDb.getReplicaSetUrl())
.run(assertExchangeWithSession((exchange) -> {
List<ResponseCookie> cookies = exchange.getResponse().getCookies().get("JSESSIONID");
assertThat(cookies).isNotEmpty();
assertThat(cookies).allMatch((cookie) -> cookie.getDomain().equals(".example.com"));

@ -56,73 +56,57 @@ class ReactiveSessionAutoConfigurationRedisTests extends AbstractSessionAutoConf
.withStartupTimeout(Duration.ofMinutes(10));
protected final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(SessionAutoConfiguration.class, WebSessionIdResolverAutoConfiguration.class));
.withClassLoader(new FilteredClassLoader(ReactiveMongoSessionRepository.class)).withConfiguration(
AutoConfigurations.of(SessionAutoConfiguration.class, WebSessionIdResolverAutoConfiguration.class,
RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class));
@Test
void defaultConfig() {
this.contextRunner.withPropertyValues("spring.session.store-type=redis")
.withConfiguration(
AutoConfigurations.of(RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class))
.run(validateSpringSessionUsesRedis("spring:session:", SaveMode.ON_SET_ATTRIBUTE));
this.contextRunner.run(validateSpringSessionUsesRedis("spring:session:", SaveMode.ON_SET_ATTRIBUTE));
}
@Test
void defaultConfigWithUniqueStoreImplementation() {
this.contextRunner.withClassLoader(new FilteredClassLoader(ReactiveMongoSessionRepository.class))
.withConfiguration(
AutoConfigurations.of(RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class))
.run(validateSpringSessionUsesRedis("spring:session:", SaveMode.ON_SET_ATTRIBUTE));
void redisTakesPrecedenceMultipleImplementations() {
ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner().withConfiguration(
AutoConfigurations.of(SessionAutoConfiguration.class, WebSessionIdResolverAutoConfiguration.class,
RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class));
contextRunner.run(validateSpringSessionUsesRedis("spring:session:", SaveMode.ON_SET_ATTRIBUTE));
}
@Test
void defaultConfigWithCustomTimeout() {
this.contextRunner.withPropertyValues("spring.session.store-type=redis", "spring.session.timeout=1m")
.withConfiguration(
AutoConfigurations.of(RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class))
.run((context) -> {
ReactiveRedisSessionRepository repository = validateSessionRepository(context,
ReactiveRedisSessionRepository.class);
assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", 60);
});
this.contextRunner.withPropertyValues("spring.session.timeout=1m").run((context) -> {
ReactiveRedisSessionRepository repository = validateSessionRepository(context,
ReactiveRedisSessionRepository.class);
assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", 60);
});
}
@Test
void defaultConfigWithCustomWebFluxTimeout() {
this.contextRunner.withPropertyValues("spring.session.store-type=redis", "server.reactive.session.timeout=1m")
.withConfiguration(
AutoConfigurations.of(RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class))
.run((context) -> {
ReactiveRedisSessionRepository repository = validateSessionRepository(context,
ReactiveRedisSessionRepository.class);
assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", 60);
});
this.contextRunner.withPropertyValues("server.reactive.session.timeout=1m").run((context) -> {
ReactiveRedisSessionRepository repository = validateSessionRepository(context,
ReactiveRedisSessionRepository.class);
assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", 60);
});
}
@Test
void redisSessionStoreWithCustomizations() {
this.contextRunner
.withConfiguration(
AutoConfigurations.of(RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class))
.withPropertyValues("spring.session.store-type=redis", "spring.session.redis.namespace=foo",
.withPropertyValues("spring.session.redis.namespace=foo",
"spring.session.redis.save-mode=on-get-attribute")
.run(validateSpringSessionUsesRedis("foo:", SaveMode.ON_GET_ATTRIBUTE));
}
@Test
void sessionCookieConfigurationIsAppliedToAutoConfiguredWebSessionIdResolver() {
this.contextRunner
.withConfiguration(
AutoConfigurations.of(RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class))
.withUserConfiguration(Config.class)
.withPropertyValues("spring.session.store-type=redis", "spring.redis.host=" + redis.getHost(),
"spring.redis.port=" + redis.getFirstMappedPort(), "spring.session.store-type=redis",
"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) -> {
this.contextRunner.withUserConfiguration(Config.class).withPropertyValues(
"spring.redis.host=" + redis.getHost(), "spring.redis.port=" + redis.getFirstMappedPort(),
"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");
assertThat(cookies).isNotEmpty();
assertThat(cookies).allMatch((cookie) -> cookie.getDomain().equals(".example.com"));

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

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

@ -60,12 +60,14 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio
.withStartupTimeout(Duration.ofMinutes(10));
protected final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withClassLoader(new FilteredClassLoader(HazelcastIndexedSessionRepository.class,
JdbcIndexedSessionRepository.class, MongoIndexedSessionRepository.class))
.withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class));
@Test
void defaultConfig() {
this.contextRunner
.withPropertyValues("spring.session.store-type=redis", "spring.redis.host=" + redis.getHost(),
.withPropertyValues("spring.redis.host=" + redis.getHost(),
"spring.redis.port=" + redis.getFirstMappedPort())
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
.run(validateSpringSessionUsesRedis("spring:session:event:0:created:", FlushMode.ON_SAVE,
@ -73,11 +75,8 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio
}
@Test
void defaultConfigWithUniqueStoreImplementation() {
this.contextRunner
.withClassLoader(new FilteredClassLoader(HazelcastIndexedSessionRepository.class,
JdbcIndexedSessionRepository.class, MongoIndexedSessionRepository.class))
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
void redisTakesPrecedenceMultipleImplementations() {
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
.withPropertyValues("spring.redis.host=" + redis.getHost(),
"spring.redis.port=" + redis.getFirstMappedPort())
.run(validateSpringSessionUsesRedis("spring:session:event:0:created:", FlushMode.ON_SAVE,
@ -87,7 +86,7 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio
@Test
void defaultConfigWithCustomTimeout() {
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")
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)).run((context) -> {
RedisIndexedSessionRepository repository = validateSessionRepository(context,
@ -99,8 +98,8 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio
@Test
void redisSessionStoreWithCustomizations() {
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
.withPropertyValues("spring.session.store-type=redis", "spring.session.redis.namespace=foo",
"spring.session.redis.flush-mode=immediate", "spring.session.redis.save-mode=on-get-attribute",
.withPropertyValues("spring.session.redis.namespace=foo", "spring.session.redis.flush-mode=immediate",
"spring.session.redis.save-mode=on-get-attribute",
"spring.session.redis.cleanup-cron=0 0 12 * * *", "spring.redis.host=" + redis.getHost(),
"spring.redis.port=" + redis.getFirstMappedPort())
.run(validateSpringSessionUsesRedis("foo:event:0:created:", FlushMode.IMMEDIATE,
@ -110,7 +109,7 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio
@Test
void redisSessionWithConfigureActionNone() {
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())
.run(validateStrategy(ConfigureRedisAction.NO_OP.getClass()));
}
@ -118,7 +117,7 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio
@Test
void redisSessionWithDefaultConfigureActionNone() {
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())
.run(validateStrategy(ConfigureNotifyKeyspaceEventsAction.class,
entry("notify-keyspace-events", "gxE")));
@ -128,7 +127,7 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio
void redisSessionWithCustomConfigureRedisActionBean() {
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.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())
.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.web.ServerProperties;
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.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.session.MapSessionRepository;
import org.springframework.session.ReactiveMapSessionRepository;
import org.springframework.session.SessionRepository;
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.web.http.CookieHttpSessionIdResolver;
import org.springframework.session.web.http.DefaultCookieSerializer;
@ -60,37 +68,31 @@ class SessionAutoConfigurationTests extends AbstractSessionAutoConfigurationTest
.withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class));
@Test
void contextFailsIfMultipleStoresAreAvailable() {
this.contextRunner.run((context) -> {
assertThat(context).hasFailed();
assertThat(context).getFailure().rootCause().isInstanceOf(NonUniqueSessionRepositoryException.class)
.hasMessageContaining("Multiple session repository candidates are available");
});
void autoConfigurationDisabledIfNoImplementationMatches() {
this.contextRunner
.withClassLoader(new FilteredClassLoader(RedisIndexedSessionRepository.class,
HazelcastIndexedSessionRepository.class, JdbcIndexedSessionRepository.class,
MongoIndexedSessionRepository.class))
.run((context) -> assertThat(context).doesNotHaveBean(SessionRepository.class));
}
@Test
void contextFailsIfStoreTypeNotAvailable() {
this.contextRunner.withPropertyValues("spring.session.store-type=jdbc").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 'jdbc'");
void backOffIfSessionRepositoryIsPresent() {
this.contextRunner.withUserConfiguration(SessionRepositoryConfiguration.class).run((context) -> {
MapSessionRepository repository = validateSessionRepository(context, MapSessionRepository.class);
assertThat(context).getBean("mySessionRepository").isSameAs(repository);
});
}
@Test
void autoConfigurationDisabledIfStoreTypeSetToNone() {
this.contextRunner.withPropertyValues("spring.session.store-type=none")
.run((context) -> assertThat(context).doesNotHaveBean(SessionRepository.class));
}
@Test
void backOffIfSessionRepositoryIsPresent() {
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);
});
void backOffIfReactiveSessionRepositoryIsPresent() {
ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class));
contextRunner.withUserConfiguration(ReactiveSessionRepositoryConfiguration.class).run((context) -> {
ReactiveMapSessionRepository repository = validateSessionRepository(context,
ReactiveMapSessionRepository.class);
assertThat(context).getBean("mySessionRepository").isSameAs(repository);
});
}
@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)
static class ServerPropertiesConfiguration {

@ -3,15 +3,25 @@
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:
* JDBC
* Redis
* Hazelcast
* 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].
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:
* 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`.
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, 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:
Similar to the servlet configuration, if you have more than one implementation, Spring Boot uses the following order for choosing a specific implementation:
[source,yaml,indent=0,subs="verbatim",configprops,configblocks]
----
spring:
session:
store-type: "jdbc"
----
. Redis
. MongoDB
. If neither Redis nor MongoDB are available, we do not configure a `ReactiveSessionRepository`.
TIP: You can disable Spring Session by setting the `store-type` to `none`.
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:

Loading…
Cancel
Save