Create spring-boot-session modules

This commit is contained in:
Andy Wilkinson
2025-04-03 15:59:55 +01:00
committed by Phillip Webb
parent 014240d576
commit e288c81b7b
69 changed files with 914 additions and 710 deletions

View File

@@ -107,6 +107,11 @@ include "spring-boot-project:spring-boot-security-oauth2-client"
include "spring-boot-project:spring-boot-security-oauth2-resource-server"
include "spring-boot-project:spring-boot-security-saml2"
include "spring-boot-project:spring-boot-sendgrid"
include "spring-boot-project:spring-boot-session"
include "spring-boot-project:spring-boot-session-data-mongodb"
include "spring-boot-project:spring-boot-session-data-redis"
include "spring-boot-project:spring-boot-session-hazelcast"
include "spring-boot-project:spring-boot-session-jdbc"
include "spring-boot-project:spring-boot-test"
include "spring-boot-project:spring-boot-test-autoconfigure"
include "spring-boot-project:spring-boot-test-integration-tests"

View File

@@ -60,6 +60,7 @@ dependencies {
optional(project(":spring-boot-project:spring-boot-security-oauth2-client"))
optional(project(":spring-boot-project:spring-boot-security-oauth2-resource-server"))
optional(project(":spring-boot-project:spring-boot-security-saml2"))
optional(project(":spring-boot-project:spring-boot-session"))
optional(project(":spring-boot-project:spring-boot-tomcat"))
optional(project(":spring-boot-project:spring-boot-undertow"))
optional(project(":spring-boot-project:spring-boot-validation"))

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 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.
@@ -27,7 +27,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.session.SessionAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.FindByIndexNameSessionRepository;
@@ -42,7 +41,7 @@ import org.springframework.session.SessionRepository;
* @author Vedran Pavic
* @since 2.0.0
*/
@AutoConfiguration(after = SessionAutoConfiguration.class)
@AutoConfiguration(afterName = "org.springframework.boot.session.autoconfigure.SessionAutoConfiguration")
@ConditionalOnClass(Session.class)
@ConditionalOnAvailableEndpoint(SessionsEndpoint.class)
public class SessionsEndpointAutoConfiguration {

View File

@@ -3,7 +3,6 @@ plugins {
id "org.springframework.boot.auto-configuration"
id "org.springframework.boot.configuration-properties"
id "org.springframework.boot.deployed"
id "org.springframework.boot.docker-test"
id "org.springframework.boot.optional-dependencies"
}
@@ -23,20 +22,6 @@ dependencies {
api(project(":spring-boot-project:spring-boot-all"))
api(project(":spring-boot-project:spring-boot-autoconfigure"))
dockerTestImplementation(project(":spring-boot-project:spring-boot-test"))
dockerTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support-docker"))
dockerTestImplementation(testFixtures(project(":spring-boot-project:spring-boot")))
dockerTestImplementation(testFixtures(project(":spring-boot-project:spring-boot-autoconfigure")))
dockerTestImplementation("com.redis:testcontainers-redis")
dockerTestImplementation("org.assertj:assertj-core")
dockerTestImplementation("org.awaitility:awaitility")
dockerTestImplementation("org.junit.jupiter:junit-jupiter")
dockerTestImplementation("org.mockito:mockito-core")
dockerTestImplementation("org.springframework:spring-test")
dockerTestImplementation("org.testcontainers:junit-jupiter")
dockerTestImplementation("org.testcontainers:mongodb")
dockerTestImplementation("org.testcontainers:testcontainers")
optional(project(":spring-boot-project:spring-boot-activemq"))
optional(project(":spring-boot-project:spring-boot-artemis"))
optional(project(":spring-boot-project:spring-boot-data-jpa"))
@@ -58,6 +43,7 @@ dependencies {
optional(project(":spring-boot-project:spring-boot-rsocket"))
optional(project(":spring-boot-project:spring-boot-security"))
optional(project(":spring-boot-project:spring-boot-security-oauth2-resource-server"))
optional(project(":spring-boot-project:spring-boot-session"))
optional(project(":spring-boot-project:spring-boot-tomcat"))
optional(project(":spring-boot-project:spring-boot-tx"))
optional(project(":spring-boot-project:spring-boot-validation"))
@@ -162,11 +148,6 @@ dependencies {
exclude group: "org.opensaml", module: "opensaml-saml-impl"
}
optional("org.springframework.security:spring-security-web")
optional("org.springframework.session:spring-session-core")
optional("org.springframework.session:spring-session-data-mongodb")
optional("org.springframework.session:spring-session-data-redis")
optional("org.springframework.session:spring-session-hazelcast")
optional("org.springframework.session:spring-session-jdbc")
optional("org.springframework.amqp:spring-rabbit")
optional("org.springframework.amqp:spring-rabbit-stream")

View File

@@ -1,61 +0,0 @@
/*
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
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.boot.context.properties.PropertyMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.config.ReactiveSessionRepositoryCustomizer;
import org.springframework.session.data.mongo.ReactiveMongoSessionRepository;
import org.springframework.session.data.mongo.config.annotation.web.reactive.ReactiveMongoWebSessionConfiguration;
/**
* Mongo-backed reactive session configuration.
*
* @author Andy Wilkinson
* @author Weix Sun
* @author Vedran Pavic
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ ReactiveMongoOperations.class, ReactiveMongoSessionRepository.class })
@ConditionalOnMissingBean(ReactiveSessionRepository.class)
@ConditionalOnBean(ReactiveMongoOperations.class)
@EnableConfigurationProperties(MongoSessionProperties.class)
@Import(ReactiveMongoWebSessionConfiguration.class)
class MongoReactiveSessionConfiguration {
@Bean
ReactiveSessionRepositoryCustomizer<ReactiveMongoSessionRepository> springBootSessionRepositoryCustomizer(
SessionProperties sessionProperties, MongoSessionProperties mongoSessionProperties,
ServerProperties serverProperties) {
return (sessionRepository) -> {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(sessionProperties.determineTimeout(() -> serverProperties.getReactive().getSession().getTimeout()))
.to(sessionRepository::setDefaultMaxInactiveInterval);
map.from(mongoSessionProperties::getCollectionName).to(sessionRepository::setCollectionName);
};
}
}

View File

@@ -1,64 +0,0 @@
/*
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
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.boot.context.properties.PropertyMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.session.SessionRepository;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.data.mongo.MongoIndexedSessionRepository;
import org.springframework.session.data.mongo.config.annotation.web.http.MongoHttpSessionConfiguration;
/**
* Mongo-backed session configuration.
*
* @author Eddú Meléndez
* @author Stephane Nicoll
* @author Vedran Pavic
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ MongoOperations.class, MongoIndexedSessionRepository.class })
@ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnBean(MongoOperations.class)
@EnableConfigurationProperties(MongoSessionProperties.class)
@Import(MongoHttpSessionConfiguration.class)
class MongoSessionConfiguration {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SessionRepositoryCustomizer<MongoIndexedSessionRepository> springBootSessionRepositoryCustomizer(
SessionProperties sessionProperties, MongoSessionProperties mongoSessionProperties,
ServerProperties serverProperties) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
return (sessionRepository) -> {
map.from(sessionProperties.determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout()))
.to(sessionRepository::setDefaultMaxInactiveInterval);
map.from(mongoSessionProperties::getCollectionName).to(sessionRepository::setCollectionName);
};
}
}

View File

@@ -1,105 +0,0 @@
/*
* Copyright 2012-2025 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.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.config.ReactiveSessionRepositoryCustomizer;
import org.springframework.session.data.redis.ReactiveRedisIndexedSessionRepository;
import org.springframework.session.data.redis.ReactiveRedisSessionRepository;
import org.springframework.session.data.redis.config.ConfigureReactiveRedisAction;
import org.springframework.session.data.redis.config.annotation.ConfigureNotifyKeyspaceEventsReactiveAction;
import org.springframework.session.data.redis.config.annotation.web.server.RedisIndexedWebSessionConfiguration;
import org.springframework.session.data.redis.config.annotation.web.server.RedisWebSessionConfiguration;
/**
* Redis-backed reactive session configuration.
*
* @author Andy Wilkinson
* @author Weix Sun
* @author Vedran Pavic
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ ReactiveRedisConnectionFactory.class, ReactiveRedisSessionRepository.class })
@ConditionalOnMissingBean(ReactiveSessionRepository.class)
@ConditionalOnBean(ReactiveRedisConnectionFactory.class)
@EnableConfigurationProperties(RedisSessionProperties.class)
class RedisReactiveSessionConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.session.redis.repository-type", havingValue = "default",
matchIfMissing = true)
@Import(RedisWebSessionConfiguration.class)
static class DefaultRedisSessionConfiguration {
@Bean
ReactiveSessionRepositoryCustomizer<ReactiveRedisSessionRepository> springBootSessionRepositoryCustomizer(
SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties,
ServerProperties serverProperties) {
return (sessionRepository) -> {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(sessionProperties
.determineTimeout(() -> serverProperties.getReactive().getSession().getTimeout()))
.to(sessionRepository::setDefaultMaxInactiveInterval);
map.from(redisSessionProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace);
map.from(redisSessionProperties::getSaveMode).to(sessionRepository::setSaveMode);
};
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.session.redis.repository-type", havingValue = "indexed")
@Import(RedisIndexedWebSessionConfiguration.class)
static class IndexedRedisSessionConfiguration {
@Bean
@ConditionalOnMissingBean
ConfigureReactiveRedisAction configureReactiveRedisAction(RedisSessionProperties redisSessionProperties) {
return switch (redisSessionProperties.getConfigureAction()) {
case NOTIFY_KEYSPACE_EVENTS -> new ConfigureNotifyKeyspaceEventsReactiveAction();
case NONE -> ConfigureReactiveRedisAction.NO_OP;
};
}
@Bean
ReactiveSessionRepositoryCustomizer<ReactiveRedisIndexedSessionRepository> springBootSessionRepositoryCustomizer(
SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties,
ServerProperties serverProperties) {
return (sessionRepository) -> {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(sessionProperties
.determineTimeout(() -> serverProperties.getReactive().getSession().getTimeout()))
.to(sessionRepository::setDefaultMaxInactiveInterval);
map.from(redisSessionProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace);
map.from(redisSessionProperties::getSaveMode).to(sessionRepository::setSaveMode);
};
}
}
}

View File

@@ -1,122 +0,0 @@
/*
* Copyright 2012-2025 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.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.session.SessionRepository;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
import org.springframework.session.data.redis.RedisSessionRepository;
import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction;
import org.springframework.session.data.redis.config.ConfigureRedisAction;
import org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration;
import org.springframework.session.data.redis.config.annotation.web.http.RedisIndexedHttpSessionConfiguration;
/**
* Redis backed session configuration.
*
* @author Andy Wilkinson
* @author Tommy Ludwig
* @author Eddú Meléndez
* @author Stephane Nicoll
* @author Vedran Pavic
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ RedisTemplate.class, RedisIndexedSessionRepository.class })
@ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@EnableConfigurationProperties(RedisSessionProperties.class)
class RedisSessionConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.session.redis.repository-type", havingValue = "default",
matchIfMissing = true)
@Import(RedisHttpSessionConfiguration.class)
static class DefaultRedisSessionConfiguration {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SessionRepositoryCustomizer<RedisSessionRepository> springBootSessionRepositoryCustomizer(
SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties,
ServerProperties serverProperties) {
String cleanupCron = redisSessionProperties.getCleanupCron();
if (cleanupCron != null) {
throw new InvalidConfigurationPropertyValueException("spring.session.redis.cleanup-cron", cleanupCron,
"Cron-based cleanup is only supported when "
+ "spring.session.redis.repository-type is set to indexed.");
}
return (sessionRepository) -> {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(sessionProperties
.determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout()))
.to(sessionRepository::setDefaultMaxInactiveInterval);
map.from(redisSessionProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace);
map.from(redisSessionProperties::getFlushMode).to(sessionRepository::setFlushMode);
map.from(redisSessionProperties::getSaveMode).to(sessionRepository::setSaveMode);
};
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.session.redis.repository-type", havingValue = "indexed")
@Import(RedisIndexedHttpSessionConfiguration.class)
static class IndexedRedisSessionConfiguration {
@Bean
@ConditionalOnMissingBean
ConfigureRedisAction configureRedisAction(RedisSessionProperties redisSessionProperties) {
return switch (redisSessionProperties.getConfigureAction()) {
case NOTIFY_KEYSPACE_EVENTS -> new ConfigureNotifyKeyspaceEventsAction();
case NONE -> ConfigureRedisAction.NO_OP;
};
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SessionRepositoryCustomizer<RedisIndexedSessionRepository> springBootSessionRepositoryCustomizer(
SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties,
ServerProperties serverProperties) {
return (sessionRepository) -> {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(sessionProperties
.determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout()))
.to(sessionRepository::setDefaultMaxInactiveInterval);
map.from(redisSessionProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace);
map.from(redisSessionProperties::getFlushMode).to(sessionRepository::setFlushMode);
map.from(redisSessionProperties::getSaveMode).to(sessionRepository::setSaveMode);
map.from(redisSessionProperties::getCleanupCron).to(sessionRepository::setCleanupCron);
};
}
}
}

View File

@@ -460,18 +460,6 @@
"description": "Whether to enable Servlet HTTP encoding support.",
"defaultValue": true
},
{
"name": "spring.session.redis.cleanup-cron",
"defaultValue": "0 * * * * *"
},
{
"name": "spring.session.servlet.filter-dispatcher-types",
"defaultValue": [
"async",
"error",
"request"
]
},
{
"name": "spring.sql.init.enabled",
"type": "java.lang.Boolean",

View File

@@ -17,7 +17,6 @@ org.springframework.boot.autoconfigure.http.client.reactive.ClientHttpConnectorA
org.springframework.boot.autoconfigure.http.client.reactive.service.ReactiveHttpServiceClientAutoConfiguration
org.springframework.boot.autoconfigure.netty.NettyAutoConfiguration
org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration
org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration

View File

@@ -24,9 +24,9 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.session.SessionAutoConfiguration;
import org.springframework.boot.http.autoconfigure.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.security.autoconfigure.servlet.SecurityAutoConfiguration;
import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor;
import org.springframework.boot.web.server.servlet.MockServletWebServer.RegisteredFilter;

View File

@@ -2043,6 +2043,11 @@ bom {
"spring-boot-security-oauth2-client",
"spring-boot-security-oauth2-resource-server",
"spring-boot-sendgrid",
"spring-boot-session",
"spring-boot-session-data-mongodb",
"spring-boot-session-data-redis",
"spring-boot-session-hazelcast",
"spring-boot-session-jdbc",
"spring-boot-starter",
"spring-boot-starter-activemq",
"spring-boot-starter-actuator",

View File

@@ -118,6 +118,11 @@ dependencies {
autoConfiguration(project(path: ":spring-boot-project:spring-boot-security-oauth2-resource-server", configuration: "autoConfigurationMetadata"))
autoConfiguration(project(path: ":spring-boot-project:spring-boot-security-saml2", configuration: "autoConfigurationMetadata"))
autoConfiguration(project(path: ":spring-boot-project:spring-boot-sendgrid", configuration: "autoConfigurationMetadata"))
autoConfiguration(project(path: ":spring-boot-project:spring-boot-session", configuration: "autoConfigurationMetadata"))
autoConfiguration(project(path: ":spring-boot-project:spring-boot-session-data-mongodb", configuration: "autoConfigurationMetadata"))
autoConfiguration(project(path: ":spring-boot-project:spring-boot-session-data-redis", configuration: "autoConfigurationMetadata"))
autoConfiguration(project(path: ":spring-boot-project:spring-boot-session-hazelcast", configuration: "autoConfigurationMetadata"))
autoConfiguration(project(path: ":spring-boot-project:spring-boot-session-jdbc", configuration: "autoConfigurationMetadata"))
autoConfiguration(project(path: ":spring-boot-project:spring-boot-testcontainers", configuration: "autoConfigurationMetadata"))
autoConfiguration(project(path: ":spring-boot-project:spring-boot-thymeleaf", configuration: "autoConfigurationMetadata"))
autoConfiguration(project(path: ":spring-boot-project:spring-boot-tomcat", configuration: "autoConfigurationMetadata"))
@@ -188,6 +193,11 @@ dependencies {
configurationProperties(project(path: ":spring-boot-project:spring-boot-security-oauth2-resource-server", configuration: "configurationPropertiesMetadata"))
configurationProperties(project(path: ":spring-boot-project:spring-boot-security-saml2", configuration: "configurationPropertiesMetadata"))
configurationProperties(project(path: ":spring-boot-project:spring-boot-sendgrid", configuration: "configurationPropertiesMetadata"))
configurationProperties(project(path: ":spring-boot-project:spring-boot-session", configuration: "configurationPropertiesMetadata"))
configurationProperties(project(path: ":spring-boot-project:spring-boot-session-data-mongodb", configuration: "configurationPropertiesMetadata"))
configurationProperties(project(path: ":spring-boot-project:spring-boot-session-data-redis", configuration: "configurationPropertiesMetadata"))
configurationProperties(project(path: ":spring-boot-project:spring-boot-session-hazelcast", configuration: "configurationPropertiesMetadata"))
configurationProperties(project(path: ":spring-boot-project:spring-boot-session-jdbc", configuration: "configurationPropertiesMetadata"))
configurationProperties(project(path: ":spring-boot-project:spring-boot-test-autoconfigure", configuration: "configurationPropertiesMetadata"))
configurationProperties(project(path: ":spring-boot-project:spring-boot-testcontainers", configuration: "configurationPropertiesMetadata"))
configurationProperties(project(path: ":spring-boot-project:spring-boot-thymeleaf", configuration: "configurationPropertiesMetadata"))

View File

@@ -0,0 +1,33 @@
plugins {
id "java-library"
id "org.springframework.boot.auto-configuration"
id "org.springframework.boot.configuration-properties"
id "org.springframework.boot.deployed"
id "org.springframework.boot.docker-test"
id "org.springframework.boot.optional-dependencies"
}
description = "Spring Boot Session Data MongoDB"
dependencies {
api(project(":spring-boot-project:spring-boot"))
api(project(":spring-boot-project:spring-boot-session"))
api("org.springframework.session:spring-session-data-mongodb")
implementation(project(":spring-boot-project:spring-boot-data-mongodb"))
optional(project(":spring-boot-project:spring-boot-autoconfigure"))
optional(project(":spring-boot-project:spring-boot-webflux"))
dockerTestImplementation(project(":spring-boot-project:spring-boot-test"))
dockerTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support-docker"))
dockerTestImplementation(testFixtures(project(":spring-boot-project:spring-boot")))
dockerTestImplementation(testFixtures(project(":spring-boot-project:spring-boot-session")))
dockerTestImplementation("org.mongodb:mongodb-driver-reactivestreams")
dockerTestImplementation("org.mongodb:mongodb-driver-sync")
dockerTestImplementation("org.testcontainers:junit-jupiter")
dockerTestImplementation("org.testcontainers:mongodb")
dockerTestRuntimeOnly("ch.qos.logback:logback-classic")
dockerTestRuntimeOnly("jakarta.servlet:jakarta.servlet-api")
}

View File

@@ -14,22 +14,22 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
package org.springframework.boot.session.data.mongodb.autoconfigure;
import java.time.Duration;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.MongoDBContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.data.mongodb.autoconfigure.MongoDataAutoConfiguration;
import org.springframework.boot.data.mongodb.autoconfigure.MongoReactiveDataAutoConfiguration;
import org.springframework.boot.mongodb.autoconfigure.MongoAutoConfiguration;
import org.springframework.boot.mongodb.autoconfigure.MongoReactiveAutoConfiguration;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.session.autoconfigure.AbstractReactiveSessionAutoConfigurationTests;
import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration;
import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext;
import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
@@ -38,27 +38,27 @@ import org.springframework.boot.webflux.autoconfigure.WebSessionIdResolverAutoCo
import org.springframework.http.ResponseCookie;
import org.springframework.session.MapSession;
import org.springframework.session.data.mongo.ReactiveMongoSessionRepository;
import org.springframework.session.data.redis.ReactiveRedisSessionRepository;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Mongo-specific tests for {@link SessionAutoConfiguration}.
* Tests for the reactive support of {@link MongoSessionAutoConfiguration}.
*
* @author Andy Wilkinson
* @author Weix Sun
*/
@Testcontainers(disabledWithoutDocker = true)
class ReactiveSessionAutoConfigurationMongoTests extends AbstractSessionAutoConfigurationTests {
class MongoReactiveSessionAutoConfigurationTests extends AbstractReactiveSessionAutoConfigurationTests {
@Container
static final MongoDBContainer mongoDb = TestImage.container(MongoDBContainer.class);
private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
.withClassLoader(new FilteredClassLoader(ReactiveRedisSessionRepository.class))
.withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class, MongoAutoConfiguration.class,
MongoDataAutoConfiguration.class, MongoReactiveAutoConfiguration.class,
MongoReactiveDataAutoConfiguration.class));
@BeforeEach
void prepareContextRunner() {
this.contextRunner = new ReactiveWebApplicationContextRunner().withConfiguration(
AutoConfigurations.of(SessionAutoConfiguration.class, MongoSessionAutoConfiguration.class,
MongoReactiveAutoConfiguration.class, MongoReactiveDataAutoConfiguration.class));
}
@Test
void defaultConfig() {
@@ -99,12 +99,8 @@ class ReactiveSessionAutoConfigurationMongoTests extends AbstractSessionAutoConf
@Test
void sessionCookieConfigurationIsAppliedToAutoConfiguredWebSessionIdResolver() {
AutoConfigurations autoConfigurations = AutoConfigurations.of(SessionAutoConfiguration.class,
MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, MongoReactiveAutoConfiguration.class,
MongoReactiveDataAutoConfiguration.class, WebSessionIdResolverAutoConfiguration.class);
new ReactiveWebApplicationContextRunner().withConfiguration(autoConfigurations)
.withUserConfiguration(Config.class)
.withClassLoader(new FilteredClassLoader(ReactiveRedisSessionRepository.class))
this.contextRunner.withConfiguration(AutoConfigurations.of(WebSessionIdResolverAutoConfiguration.class))
.withUserConfiguration(ReactiveWebServerConfiguration.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",

View File

@@ -14,10 +14,11 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
package org.springframework.boot.session.data.mongodb.autoconfigure;
import java.time.Duration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.MongoDBContainer;
import org.testcontainers.junit.jupiter.Container;
@@ -27,7 +28,8 @@ import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.data.mongodb.autoconfigure.MongoDataAutoConfiguration;
import org.springframework.boot.mongodb.autoconfigure.MongoAutoConfiguration;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.session.autoconfigure.AbstractSessionAutoConfigurationTests;
import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration;
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
@@ -36,29 +38,27 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.config.SessionRepositoryCustomizer;
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 static org.assertj.core.api.Assertions.assertThat;
/**
* Mongo-specific tests for {@link SessionAutoConfiguration}.
* Tests for the servlet support of {@link MongoSessionAutoConfiguration}.
*
* @author Andy Wilkinson
*/
@Testcontainers(disabledWithoutDocker = true)
class SessionAutoConfigurationMongoTests extends AbstractSessionAutoConfigurationTests {
class MongoSessionAutoConfigurationTests extends AbstractSessionAutoConfigurationTests {
@Container
static final MongoDBContainer mongoDb = TestImage.container(MongoDBContainer.class);
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());
@BeforeEach
void prepareContextRunner() {
this.contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class,
SessionAutoConfiguration.class, MongoSessionAutoConfiguration.class))
.withPropertyValues("spring.data.mongodb.uri=" + mongoDb.getReplicaSetUrl());
}
@Test
void defaultConfig() {

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2012-2025 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.session.data.mongodb.autoconfigure;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.data.mongodb.autoconfigure.MongoDataAutoConfiguration;
import org.springframework.boot.data.mongodb.autoconfigure.MongoReactiveDataAutoConfiguration;
import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration;
import org.springframework.boot.session.autoconfigure.SessionProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.SessionRepository;
import org.springframework.session.config.ReactiveSessionRepositoryCustomizer;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.data.mongo.MongoIndexedSessionRepository;
import org.springframework.session.data.mongo.ReactiveMongoSessionRepository;
import org.springframework.session.data.mongo.config.annotation.web.http.MongoHttpSessionConfiguration;
import org.springframework.session.data.mongo.config.annotation.web.reactive.ReactiveMongoWebSessionConfiguration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Session Data MongoDB.
*
* @author Eddú Meléndez
* @author Stephane Nicoll
* @author Vedran Pavic
* @since 4.0.0
*/
@AutoConfiguration(before = SessionAutoConfiguration.class,
beforeName = { "org.springframework.boot.webflux.autoconfigure.HttpHandlerAutoConfiguration",
"org.springframework.boot.webflux.autoconfigure.WebFluxAutoConfiguration" },
after = { MongoDataAutoConfiguration.class, MongoReactiveDataAutoConfiguration.class },
afterName = "org.springframework.boot.webflux.autoconfigure.WebSessionIdResolverAutoConfiguration")
@EnableConfigurationProperties(MongoSessionProperties.class)
public class MongoSessionAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ MongoOperations.class, MongoIndexedSessionRepository.class })
@ConditionalOnBean(MongoOperations.class)
@ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@Import(MongoHttpSessionConfiguration.class)
class ServletMongoSessionConfiguration {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SessionRepositoryCustomizer<MongoIndexedSessionRepository> springBootSessionRepositoryCustomizer(
SessionProperties sessionProperties, MongoSessionProperties mongoSessionProperties,
ServerProperties serverProperties) {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
return (sessionRepository) -> {
map.from(sessionProperties
.determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout()))
.to(sessionRepository::setDefaultMaxInactiveInterval);
map.from(mongoSessionProperties::getCollectionName).to(sessionRepository::setCollectionName);
};
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ ReactiveMongoOperations.class, ReactiveMongoSessionRepository.class })
@ConditionalOnMissingBean(ReactiveSessionRepository.class)
@ConditionalOnBean(ReactiveMongoOperations.class)
@Import(ReactiveMongoWebSessionConfiguration.class)
class ReactiveMongoSessionConfiguration {
@Bean
ReactiveSessionRepositoryCustomizer<ReactiveMongoSessionRepository> springBootSessionRepositoryCustomizer(
SessionProperties sessionProperties, MongoSessionProperties mongoSessionProperties,
ServerProperties serverProperties) {
return (sessionRepository) -> {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(sessionProperties
.determineTimeout(() -> serverProperties.getReactive().getSession().getTimeout()))
.to(sessionRepository::setDefaultMaxInactiveInterval);
map.from(mongoSessionProperties::getCollectionName).to(sessionRepository::setCollectionName);
};
}
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
package org.springframework.boot.session.data.mongodb.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -22,7 +22,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* Configuration properties for Mongo-backed Spring Session.
*
* @author Andy Wilkinson
* @since 2.0.0
* @since 4.0.0
*/
@ConfigurationProperties("spring.session.mongodb")
public class MongoSessionProperties {

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2025 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.
*/
/**
* Auto-configuration for Spring Session Data MongoDB.
*/
package org.springframework.boot.session.data.mongodb.autoconfigure;

View File

@@ -0,0 +1 @@
org.springframework.boot.session.data.mongodb.autoconfigure.MongoSessionAutoConfiguration

View File

@@ -0,0 +1,37 @@
plugins {
id "java-library"
id "org.springframework.boot.auto-configuration"
id "org.springframework.boot.configuration-properties"
id "org.springframework.boot.deployed"
id "org.springframework.boot.docker-test"
id "org.springframework.boot.optional-dependencies"
}
description = "Spring Boot Session Data Redis"
dependencies {
api(project(":spring-boot-project:spring-boot"))
api(project(":spring-boot-project:spring-boot-session"))
api("org.springframework.session:spring-session-data-redis")
implementation(project(":spring-boot-project:spring-boot-data-redis"))
optional(project(":spring-boot-project:spring-boot-autoconfigure"))
optional(project(":spring-boot-project:spring-boot-webflux"))
dockerTestImplementation(project(":spring-boot-project:spring-boot-test"))
dockerTestImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support-docker"))
dockerTestImplementation(testFixtures(project(":spring-boot-project:spring-boot")))
dockerTestImplementation(testFixtures(project(":spring-boot-project:spring-boot-session")))
dockerTestImplementation("com.redis:testcontainers-redis")
dockerTestImplementation("org.testcontainers:junit-jupiter")
dockerTestRuntimeOnly("jakarta.servlet:jakarta.servlet-api")
testImplementation(project(":spring-boot-project:spring-boot-test"))
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation("jakarta.servlet:jakarta.servlet-api")
testImplementation("org.springframework:spring-web")
testRuntimeOnly("ch.qos.logback:logback-classic")
}

View File

@@ -14,13 +14,14 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
package org.springframework.boot.session.data.redis.autoconfigure;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import com.redis.testcontainers.RedisContainer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
@@ -30,7 +31,8 @@ import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.data.redis.autoconfigure.RedisAutoConfiguration;
import org.springframework.boot.data.redis.autoconfigure.RedisReactiveAutoConfiguration;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.session.autoconfigure.AbstractReactiveSessionAutoConfigurationTests;
import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration;
import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext;
import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
@@ -42,7 +44,6 @@ import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.ResponseCookie;
import org.springframework.session.MapSession;
import org.springframework.session.SaveMode;
import org.springframework.session.data.mongo.ReactiveMongoSessionRepository;
import org.springframework.session.data.redis.ReactiveRedisIndexedSessionRepository;
import org.springframework.session.data.redis.ReactiveRedisSessionRepository;
import org.springframework.session.data.redis.config.ConfigureReactiveRedisAction;
@@ -52,7 +53,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
/**
* Reactive Redis-specific tests for {@link SessionAutoConfiguration}.
* Tests for the reactive support of {@link RedisSessionAutoConfiguration}.
*
* @author Stephane Nicoll
* @author Andy Wilkinson
@@ -60,30 +61,24 @@ import static org.assertj.core.api.Assertions.entry;
* @author Weix Sun
*/
@Testcontainers(disabledWithoutDocker = true)
class ReactiveSessionAutoConfigurationRedisTests extends AbstractSessionAutoConfigurationTests {
class ReactiveRedisSessionAutoConfigurationTests extends AbstractReactiveSessionAutoConfigurationTests {
@Container
public static RedisContainer redis = TestImage.container(RedisContainer.class);
protected final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
.withClassLoader(new FilteredClassLoader(ReactiveMongoSessionRepository.class))
.withConfiguration(
AutoConfigurations.of(SessionAutoConfiguration.class, WebSessionIdResolverAutoConfiguration.class,
RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class));
@BeforeEach
void prepareRunner() {
this.contextRunner = new ReactiveWebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class,
WebSessionIdResolverAutoConfiguration.class, RedisSessionAutoConfiguration.class,
RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class));
}
@Test
void defaultConfig() {
this.contextRunner.run(validateSpringSessionUsesRedis("spring:session:", SaveMode.ON_SET_ATTRIBUTE));
}
@Test
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.timeout=1m").run((context) -> {
@@ -111,7 +106,7 @@ class ReactiveSessionAutoConfigurationRedisTests extends AbstractSessionAutoConf
@Test
void sessionCookieConfigurationIsAppliedToAutoConfiguredWebSessionIdResolver() {
this.contextRunner.withUserConfiguration(Config.class)
this.contextRunner.withUserConfiguration(ReactiveWebServerConfiguration.class)
.withPropertyValues("spring.data.redis.host=" + redis.getHost(),
"spring.data.redis.port=" + redis.getFirstMappedPort(),
"server.reactive.session.cookie.name:JSESSIONID",
@@ -136,13 +131,12 @@ class ReactiveSessionAutoConfigurationRedisTests extends AbstractSessionAutoConf
this.contextRunner
.withPropertyValues("spring.session.redis.repository-type=indexed",
"spring.data.redis.host=" + redis.getHost(), "spring.data.redis.port=" + redis.getFirstMappedPort())
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
.run(validateSpringSessionUsesIndexedRedis("spring:session:", SaveMode.ON_SET_ATTRIBUTE));
}
@Test
void indexedRedisSessionStoreWithCustomizations() {
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
this.contextRunner
.withPropertyValues("spring.session.redis.repository-type=indexed", "spring.session.redis.namespace=foo",
"spring.session.redis.save-mode=on-get-attribute", "spring.data.redis.host=" + redis.getHost(),
"spring.data.redis.port=" + redis.getFirstMappedPort())
@@ -151,7 +145,7 @@ class ReactiveSessionAutoConfigurationRedisTests extends AbstractSessionAutoConf
@Test
void indexedRedisSessionWithConfigureActionNone() {
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
this.contextRunner
.withPropertyValues("spring.session.redis.repository-type=indexed",
"spring.session.redis.configure-action=none", "spring.data.redis.host=" + redis.getHost(),
"spring.data.redis.port=" + redis.getFirstMappedPort())
@@ -160,7 +154,7 @@ class ReactiveSessionAutoConfigurationRedisTests extends AbstractSessionAutoConf
@Test
void indexedRedisSessionWithDefaultConfigureActionNone() {
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
this.contextRunner
.withPropertyValues("spring.session.redis.repository-type=indexed",
"spring.data.redis.host=" + redis.getHost(), "spring.data.redis.port=" + redis.getFirstMappedPort())
.run(validateStrategy(ConfigureNotifyKeyspaceEventsReactiveAction.class,
@@ -169,8 +163,7 @@ class ReactiveSessionAutoConfigurationRedisTests extends AbstractSessionAutoConf
@Test
void indexedRedisSessionWithCustomConfigureReactiveRedisActionBean() {
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
.withUserConfiguration(MaxEntriesReactiveRedisAction.class)
this.contextRunner.withUserConfiguration(MaxEntriesReactiveRedisAction.class)
.withPropertyValues("spring.session.redis.repository-type=indexed",
"spring.data.redis.host=" + redis.getHost(), "spring.data.redis.port=" + redis.getFirstMappedPort())
.run(validateStrategy(MaxEntriesReactiveRedisAction.class, entry("set-max-intset-entries", "1024")));

View File

@@ -14,12 +14,13 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
package org.springframework.boot.session.data.redis.autoconfigure;
import java.time.Duration;
import java.util.Map;
import com.redis.testcontainers.RedisContainer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
@@ -28,7 +29,8 @@ import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
import org.springframework.boot.data.redis.autoconfigure.RedisAutoConfiguration;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.session.autoconfigure.AbstractSessionAutoConfigurationTests;
import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration;
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
@@ -40,33 +42,31 @@ import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.session.FlushMode;
import org.springframework.session.SaveMode;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.data.mongo.MongoIndexedSessionRepository;
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
import org.springframework.session.data.redis.RedisSessionRepository;
import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction;
import org.springframework.session.data.redis.config.ConfigureRedisAction;
import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository;
import org.springframework.session.jdbc.JdbcIndexedSessionRepository;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
/**
* Redis specific tests for {@link SessionAutoConfiguration}.
* Tests for {@link RedisSessionAutoConfiguration}.
*
* @author Stephane Nicoll
* @author Vedran Pavic
*/
@Testcontainers(disabledWithoutDocker = true)
class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfigurationTests {
class RedisSessionAutoConfigurationTests extends AbstractSessionAutoConfigurationTests {
@Container
public static RedisContainer redis = TestImage.container(RedisContainer.class);
protected final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withClassLoader(new FilteredClassLoader(HazelcastIndexedSessionRepository.class,
JdbcIndexedSessionRepository.class, MongoIndexedSessionRepository.class))
.withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class));
@BeforeEach
void prepareContextRunner() {
this.contextRunner = new WebApplicationContextRunner().withConfiguration(AutoConfigurations
.of(SessionAutoConfiguration.class, RedisSessionAutoConfiguration.class, RedisAutoConfiguration.class));
}
@Test
void defaultConfig() {
@@ -90,21 +90,11 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio
});
}
@Test
void redisTakesPrecedenceMultipleImplementations() {
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
.withPropertyValues("spring.data.redis.host=" + redis.getHost(),
"spring.data.redis.port=" + redis.getFirstMappedPort())
.run(validateSpringSessionUsesDefaultRedis("spring:session:", FlushMode.ON_SAVE,
SaveMode.ON_SET_ATTRIBUTE));
}
@Test
void defaultConfigWithCustomTimeout() {
this.contextRunner
.withPropertyValues("spring.data.redis.host=" + redis.getHost(),
"spring.data.redis.port=" + redis.getFirstMappedPort(), "spring.session.timeout=1m")
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
.run((context) -> {
RedisSessionRepository repository = validateSessionRepository(context, RedisSessionRepository.class);
assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval", Duration.ofMinutes(1));
@@ -113,7 +103,7 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio
@Test
void defaultRedisSessionStoreWithCustomizations() {
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
this.contextRunner
.withPropertyValues("spring.session.redis.namespace=foo", "spring.session.redis.flush-mode=immediate",
"spring.session.redis.save-mode=on-get-attribute", "spring.data.redis.host=" + redis.getHost(),
"spring.data.redis.port=" + redis.getFirstMappedPort())
@@ -132,7 +122,7 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio
@Test
void indexedRedisSessionStoreWithCustomizations() {
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
this.contextRunner
.withPropertyValues("spring.session.redis.repository-type=indexed", "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.data.redis.host=" + redis.getHost(),
@@ -143,7 +133,7 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio
@Test
void indexedRedisSessionWithConfigureActionNone() {
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
this.contextRunner
.withPropertyValues("spring.session.redis.repository-type=indexed",
"spring.session.redis.configure-action=none", "spring.data.redis.host=" + redis.getHost(),
"spring.data.redis.port=" + redis.getFirstMappedPort())
@@ -152,7 +142,7 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio
@Test
void indexedRedisSessionWithDefaultConfigureActionNone() {
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
this.contextRunner
.withPropertyValues("spring.session.redis.repository-type=indexed",
"spring.data.redis.host=" + redis.getHost(), "spring.data.redis.port=" + redis.getFirstMappedPort())
.run(validateStrategy(ConfigureNotifyKeyspaceEventsAction.class, entry("notify-keyspace-events", "gxE")));
@@ -160,8 +150,7 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio
@Test
void indexedRedisSessionWithCustomConfigureRedisActionBean() {
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
.withUserConfiguration(MaxEntriesRedisAction.class)
this.contextRunner.withUserConfiguration(MaxEntriesRedisAction.class)
.withPropertyValues("spring.session.redis.repository-type=indexed",
"spring.data.redis.host=" + redis.getHost(), "spring.data.redis.port=" + redis.getFirstMappedPort())
.run(validateStrategy(MaxEntriesRedisAction.class, entry("set-max-intset-entries", "1024")));
@@ -170,8 +159,7 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio
@Test
void whenTheUserDefinesTheirOwnSessionRepositoryCustomizerThenDefaultConfigurationIsOverwritten() {
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
.withUserConfiguration(CustomizerConfiguration.class)
this.contextRunner.withUserConfiguration(CustomizerConfiguration.class)
.withPropertyValues("spring.session.redis.flush-mode=immediate",
"spring.data.redis.host=" + redis.getHost(), "spring.data.redis.port=" + redis.getFirstMappedPort())
.run((context) -> {
@@ -182,8 +170,7 @@ class SessionAutoConfigurationRedisTests extends AbstractSessionAutoConfiguratio
@Test
void whenIndexedAndTheUserDefinesTheirOwnSessionRepositoryCustomizerThenDefaultConfigurationIsOverwritten() {
this.contextRunner.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class))
.withUserConfiguration(IndexedCustomizerConfiguration.class)
this.contextRunner.withUserConfiguration(IndexedCustomizerConfiguration.class)
.withPropertyValues("spring.session.redis.repository-type=indexed",
"spring.session.redis.flush-mode=immediate", "spring.data.redis.host=" + redis.getHost(),
"spring.data.redis.port=" + redis.getFirstMappedPort())

View File

@@ -0,0 +1,211 @@
/*
* Copyright 2012-2025 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.session.data.redis.autoconfigure;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
import org.springframework.boot.data.redis.autoconfigure.RedisAutoConfiguration;
import org.springframework.boot.data.redis.autoconfigure.RedisReactiveAutoConfiguration;
import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration;
import org.springframework.boot.session.autoconfigure.SessionProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.SessionRepository;
import org.springframework.session.config.ReactiveSessionRepositoryCustomizer;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.data.redis.ReactiveRedisIndexedSessionRepository;
import org.springframework.session.data.redis.ReactiveRedisSessionRepository;
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
import org.springframework.session.data.redis.RedisSessionRepository;
import org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction;
import org.springframework.session.data.redis.config.ConfigureReactiveRedisAction;
import org.springframework.session.data.redis.config.ConfigureRedisAction;
import org.springframework.session.data.redis.config.annotation.ConfigureNotifyKeyspaceEventsReactiveAction;
import org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration;
import org.springframework.session.data.redis.config.annotation.web.http.RedisIndexedHttpSessionConfiguration;
import org.springframework.session.data.redis.config.annotation.web.server.RedisIndexedWebSessionConfiguration;
import org.springframework.session.data.redis.config.annotation.web.server.RedisWebSessionConfiguration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Session Data Redis.
*
* @author Andy Wilkinson
* @author Tommy Ludwig
* @author Eddú Meléndez
* @author Stephane Nicoll
* @author Vedran Pavic
* @since 4.0.0
*/
@AutoConfiguration(before = SessionAutoConfiguration.class,
beforeName = { "org.springframework.boot.webflux.autoconfigure.HttpHandlerAutoConfiguration",
"org.springframework.boot.webflux.autoconfigure.WebFluxAutoConfiguration" },
after = { RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class },
afterName = "org.springframework.boot.webflux.autoconfigure.WebSessionIdResolverAutoConfiguration")
@EnableConfigurationProperties({ RedisSessionProperties.class, ServerProperties.class, SessionProperties.class })
public class RedisSessionAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ RedisTemplate.class, RedisIndexedSessionRepository.class })
@ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
static class ServletRedisSessionConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.session.redis.repository-type", havingValue = "default",
matchIfMissing = true)
@Import(RedisHttpSessionConfiguration.class)
static class DefaultRedisSessionConfiguration {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SessionRepositoryCustomizer<RedisSessionRepository> springBootSessionRepositoryCustomizer(
SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties,
ServerProperties serverProperties) {
String cleanupCron = redisSessionProperties.getCleanupCron();
if (cleanupCron != null) {
throw new InvalidConfigurationPropertyValueException("spring.session.redis.cleanup-cron",
cleanupCron, "Cron-based cleanup is only supported when "
+ "spring.session.redis.repository-type is set to indexed.");
}
return (sessionRepository) -> {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(sessionProperties
.determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout()))
.to(sessionRepository::setDefaultMaxInactiveInterval);
map.from(redisSessionProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace);
map.from(redisSessionProperties::getFlushMode).to(sessionRepository::setFlushMode);
map.from(redisSessionProperties::getSaveMode).to(sessionRepository::setSaveMode);
};
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.session.redis.repository-type", havingValue = "indexed")
@Import(RedisIndexedHttpSessionConfiguration.class)
static class IndexedRedisSessionConfiguration {
@Bean
@ConditionalOnMissingBean
ConfigureRedisAction configureRedisAction(RedisSessionProperties redisSessionProperties) {
return switch (redisSessionProperties.getConfigureAction()) {
case NOTIFY_KEYSPACE_EVENTS -> new ConfigureNotifyKeyspaceEventsAction();
case NONE -> ConfigureRedisAction.NO_OP;
};
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SessionRepositoryCustomizer<RedisIndexedSessionRepository> springBootSessionRepositoryCustomizer(
SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties,
ServerProperties serverProperties) {
return (sessionRepository) -> {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(sessionProperties
.determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout()))
.to(sessionRepository::setDefaultMaxInactiveInterval);
map.from(redisSessionProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace);
map.from(redisSessionProperties::getFlushMode).to(sessionRepository::setFlushMode);
map.from(redisSessionProperties::getSaveMode).to(sessionRepository::setSaveMode);
map.from(redisSessionProperties::getCleanupCron).to(sessionRepository::setCleanupCron);
};
}
}
}
@ConditionalOnWebApplication(type = Type.REACTIVE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ ReactiveRedisConnectionFactory.class, ReactiveRedisSessionRepository.class })
@ConditionalOnMissingBean(ReactiveSessionRepository.class)
@ConditionalOnBean(ReactiveRedisConnectionFactory.class)
class ReactiveRedisSessionConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.session.redis.repository-type", havingValue = "default",
matchIfMissing = true)
@Import(RedisWebSessionConfiguration.class)
static class DefaultRedisSessionConfiguration {
@Bean
ReactiveSessionRepositoryCustomizer<ReactiveRedisSessionRepository> springBootSessionRepositoryCustomizer(
SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties,
ServerProperties serverProperties) {
return (sessionRepository) -> {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(sessionProperties
.determineTimeout(() -> serverProperties.getReactive().getSession().getTimeout()))
.to(sessionRepository::setDefaultMaxInactiveInterval);
map.from(redisSessionProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace);
map.from(redisSessionProperties::getSaveMode).to(sessionRepository::setSaveMode);
};
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.session.redis.repository-type", havingValue = "indexed")
@Import(RedisIndexedWebSessionConfiguration.class)
static class IndexedRedisSessionConfiguration {
@Bean
@ConditionalOnMissingBean
ConfigureReactiveRedisAction configureReactiveRedisAction(RedisSessionProperties redisSessionProperties) {
return switch (redisSessionProperties.getConfigureAction()) {
case NOTIFY_KEYSPACE_EVENTS -> new ConfigureNotifyKeyspaceEventsReactiveAction();
case NONE -> ConfigureReactiveRedisAction.NO_OP;
};
}
@Bean
ReactiveSessionRepositoryCustomizer<ReactiveRedisIndexedSessionRepository> springBootSessionRepositoryCustomizer(
SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties,
ServerProperties serverProperties) {
return (sessionRepository) -> {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(sessionProperties
.determineTimeout(() -> serverProperties.getReactive().getSession().getTimeout()))
.to(sessionRepository::setDefaultMaxInactiveInterval);
map.from(redisSessionProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace);
map.from(redisSessionProperties::getSaveMode).to(sessionRepository::setSaveMode);
};
}
}
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
package org.springframework.boot.session.data.redis.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.session.FlushMode;
@@ -24,7 +24,7 @@ import org.springframework.session.SaveMode;
* Configuration properties for Redis backed Spring Session.
*
* @author Vedran Pavic
* @since 2.0.0
* @since 4.0.0
*/
@ConfigurationProperties("spring.session.redis")
public class RedisSessionProperties {

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2025 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.
*/
/**
* Auto-configuration for Spring Session Data Redis.
*/
package org.springframework.boot.session.data.redis.autoconfigure;

View File

@@ -0,0 +1,8 @@
{
"properties": [
{
"name": "spring.session.redis.cleanup-cron",
"defaultValue": "0 * * * * *"
}
]
}

View File

@@ -0,0 +1 @@
org.springframework.boot.session.data.redis.autoconfigure.RedisSessionAutoConfiguration

View File

@@ -0,0 +1,27 @@
plugins {
id "java-library"
id "org.springframework.boot.auto-configuration"
id "org.springframework.boot.configuration-properties"
id "org.springframework.boot.deployed"
id "org.springframework.boot.optional-dependencies"
}
description = "Spring Boot Session Hazelcast"
dependencies {
api(project(":spring-boot-project:spring-boot"))
api(project(":spring-boot-project:spring-boot-session"))
api("org.springframework.session:spring-session-hazelcast")
implementation(project(":spring-boot-project:spring-boot-hazelcast"))
optional(project(":spring-boot-project:spring-boot-autoconfigure"))
testImplementation(project(":spring-boot-project:spring-boot-test"))
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot-session")))
testImplementation("jakarta.servlet:jakarta.servlet-api")
testImplementation("org.springframework:spring-web")
testRuntimeOnly("ch.qos.logback:logback-classic")
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 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.
@@ -14,18 +14,23 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
package org.springframework.boot.session.hazelcast.autoconfigure;
import com.hazelcast.core.HazelcastInstance;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration;
import org.springframework.boot.session.autoconfigure.SessionProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
@@ -35,20 +40,22 @@ import org.springframework.session.hazelcast.HazelcastIndexedSessionRepository;
import org.springframework.session.hazelcast.config.annotation.web.http.HazelcastHttpSessionConfiguration;
/**
* Hazelcast backed session configuration.
* {@link EnableAutoConfiguration Auto-configuration} for Spring Session Hazelcast.
*
* @author Tommy Ludwig
* @author Eddú Meléndez
* @author Stephane Nicoll
* @author Vedran Pavic
* @since 4.0.0
*/
@Configuration(proxyBeanMethods = false)
@AutoConfiguration(before = SessionAutoConfiguration.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(HazelcastIndexedSessionRepository.class)
@ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnBean(HazelcastInstance.class)
@EnableConfigurationProperties(HazelcastSessionProperties.class)
@EnableConfigurationProperties({ HazelcastSessionProperties.class, ServerProperties.class, SessionProperties.class })
@Import(HazelcastHttpSessionConfiguration.class)
class HazelcastSessionConfiguration {
public class HazelcastSessionAutoConfiguration {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
package org.springframework.boot.session.hazelcast.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.session.FlushMode;
@@ -24,7 +24,7 @@ import org.springframework.session.SaveMode;
* Configuration properties for Hazelcast backed Spring Session.
*
* @author Vedran Pavic
* @since 2.0.0
* @since 4.0.0
*/
@ConfigurationProperties("spring.session.hazelcast")
public class HazelcastSessionProperties {

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2025 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.
*/
/**
* Auto-configuration for Spring Session Hazelcast.
*/
package org.springframework.boot.session.hazelcast.autoconfigure;

View File

@@ -0,0 +1 @@
org.springframework.boot.session.hazelcast.autoconfigure.HazelcastSessionAutoConfiguration

View File

@@ -14,28 +14,26 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
package org.springframework.boot.session.hazelcast.autoconfigure;
import java.time.Duration;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.map.IMap;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
import org.springframework.boot.session.autoconfigure.AbstractSessionAutoConfigurationTests;
import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.FlushMode;
import org.springframework.session.SaveMode;
import org.springframework.session.config.SessionRepositoryCustomizer;
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 static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
@@ -43,29 +41,30 @@ import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
/**
* Hazelcast specific tests for {@link SessionAutoConfiguration}.
* Tests for {@link HazelcastSessionAutoConfiguration}.
*
* @author Vedran Pavic
*/
class SessionAutoConfigurationHazelcastTests extends AbstractSessionAutoConfigurationTests {
class HazelcastSessionAutoConfigurationTests 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.run(this::validateDefaultConfig);
@BeforeEach
void prepareContextRunner() {
this.contextRunner = new WebApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(SessionAutoConfiguration.class, HazelcastSessionAutoConfiguration.class))
.withUserConfiguration(HazelcastConfiguration.class);
}
@Test
void hazelcastTakesPrecedenceOverMongo() {
this.contextRunner
.withClassLoader(
new FilteredClassLoader(RedisIndexedSessionRepository.class, JdbcIndexedSessionRepository.class))
.run(this::validateDefaultConfig);
void defaultConfig() {
this.contextRunner.run((context) -> {
HazelcastIndexedSessionRepository repository = validateSessionRepository(context,
HazelcastIndexedSessionRepository.class);
assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval",
new ServerProperties().getServlet().getSession().getTimeout());
HazelcastInstance hazelcastInstance = context.getBean(HazelcastInstance.class);
then(hazelcastInstance).should().getMap("spring:session:sessions");
});
}
@Test
@@ -77,15 +76,6 @@ class SessionAutoConfigurationHazelcastTests extends AbstractSessionAutoConfigur
});
}
private void validateDefaultConfig(AssertableWebApplicationContext context) {
HazelcastIndexedSessionRepository repository = validateSessionRepository(context,
HazelcastIndexedSessionRepository.class);
assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval",
new ServerProperties().getServlet().getSession().getTimeout());
HazelcastInstance hazelcastInstance = context.getBean(HazelcastInstance.class);
then(hazelcastInstance).should().getMap("spring:session:sessions");
}
@Test
void customMapName() {
this.contextRunner.withPropertyValues("spring.session.hazelcast.map-name=foo:bar:biz").run((context) -> {

View File

@@ -0,0 +1,32 @@
plugins {
id "java-library"
id "org.springframework.boot.auto-configuration"
id "org.springframework.boot.configuration-properties"
id "org.springframework.boot.deployed"
id "org.springframework.boot.optional-dependencies"
}
description = "Spring Boot Session JDBC"
dependencies {
api(project(":spring-boot-project:spring-boot"))
api(project(":spring-boot-project:spring-boot-session"))
api("org.springframework.session:spring-session-jdbc")
implementation(project(":spring-boot-project:spring-boot-jdbc"))
optional(project(":spring-boot-project:spring-boot-autoconfigure"))
testImplementation(project(":spring-boot-project:spring-boot-test"))
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation(project(":spring-boot-project:spring-boot-flyway"))
testImplementation(project(":spring-boot-project:spring-boot-liquibase"))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot-session")))
testImplementation("jakarta.servlet:jakarta.servlet-api")
testImplementation("org.apache.commons:commons-dbcp2")
testImplementation("org.springframework:spring-web")
testRuntimeOnly("ch.qos.logback:logback-classic")
testRuntimeOnly("org.flywaydb:flyway-database-hsqldb")
testRuntimeOnly("org.hsqldb:hsqldb")
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 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.
@@ -14,22 +14,27 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
package org.springframework.boot.session.jdbc.autoconfigure;
import javax.sql.DataSource;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.sql.init.OnDatabaseInitializationCondition;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration;
import org.springframework.boot.session.autoconfigure.SessionProperties;
import org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer;
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.core.Ordered;
import org.springframework.core.annotation.Order;
@@ -41,19 +46,21 @@ import org.springframework.session.jdbc.config.annotation.SpringSessionDataSourc
import org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessionConfiguration;
/**
* JDBC backed session configuration.
* {@link EnableAutoConfiguration Auto-configuraion} for Spring Session JDBC.
*
* @author Eddú Meléndez
* @author Stephane Nicoll
* @author Vedran Pavic
* @since 4.0.0
*/
@Configuration(proxyBeanMethods = false)
@AutoConfiguration(before = SessionAutoConfiguration.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ JdbcTemplate.class, JdbcIndexedSessionRepository.class })
@ConditionalOnMissingBean(SessionRepository.class)
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(JdbcSessionProperties.class)
@EnableConfigurationProperties({ JdbcSessionProperties.class, SessionProperties.class })
@Import({ DatabaseInitializationDependencyConfigurer.class, JdbcHttpSessionConfiguration.class })
class JdbcSessionConfiguration {
public class JdbcSessionAutoConfiguration {
@Bean
@ConditionalOnMissingBean

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
package org.springframework.boot.session.jdbc.autoconfigure;
import java.util.List;
@@ -34,7 +34,7 @@ import org.springframework.util.StringUtils;
* @author Vedran Pavic
* @author Andy Wilkinson
* @author Phillip Webb
* @since 2.6.0
* @since 4.0.0
*/
public class JdbcSessionDataSourceScriptDatabaseInitializer extends DataSourceScriptDatabaseInitializer {

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
package org.springframework.boot.session.jdbc.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.sql.init.DatabaseInitializationMode;
@@ -22,10 +22,10 @@ import org.springframework.session.FlushMode;
import org.springframework.session.SaveMode;
/**
* Configuration properties for JDBC backed Spring Session.
* Configuration properties for JDBC-backed Spring Session.
*
* @author Vedran Pavic
* @since 2.0.0
* @since 4.0.0
*/
@ConfigurationProperties("spring.session.jdbc")
public class JdbcSessionProperties {

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2025 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.
*/
/**
* Auto-configuration for Spring Session JDBC.
*/
package org.springframework.boot.session.jdbc.autoconfigure;

View File

@@ -1,3 +1,3 @@
# Depends on Database Initialization Detectors
org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector=\
org.springframework.boot.autoconfigure.session.JdbcIndexedSessionRepositoryDependsOnDatabaseInitializationDetector
org.springframework.boot.session.jdbc.autoconfigure.JdbcIndexedSessionRepositoryDependsOnDatabaseInitializationDetector

View File

@@ -0,0 +1 @@
org.springframework.boot.session.jdbc.autoconfigure.JdbcSessionAutoConfiguration

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
package org.springframework.boot.session.jdbc.autoconfigure;
import java.time.Duration;
@@ -33,13 +33,12 @@ import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerA
import org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration;
import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
import org.springframework.boot.liquibase.autoconfigure.LiquibaseAutoConfiguration;
import org.springframework.boot.session.autoconfigure.AbstractSessionAutoConfigurationTests;
import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration;
import org.springframework.boot.sql.init.DatabaseInitializationMode;
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.testsupport.classpath.resources.WithResource;
import org.springframework.boot.web.servlet.AbstractFilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@@ -47,9 +46,6 @@ import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.session.FlushMode;
import org.springframework.session.SaveMode;
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.jdbc.PostgreSqlJdbcIndexedSessionRepositoryCustomizer;
import org.springframework.session.jdbc.config.annotation.SpringSessionDataSource;
@@ -58,55 +54,34 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* JDBC specific tests for {@link SessionAutoConfiguration}.
* Tests for {@link JdbcSessionAutoConfiguration}.
*
* @author Vedran Pavic
* @author Stephane Nicoll
*/
class SessionAutoConfigurationJdbcTests extends AbstractSessionAutoConfigurationTests {
private WebApplicationContextRunner contextRunner;
class JdbcSessionAutoConfigurationTests extends AbstractSessionAutoConfigurationTests {
@BeforeEach
void prepareRunner() {
this.contextRunner = new WebApplicationContextRunner()
.withClassLoader(new FilteredClassLoader(Thread.currentThread().getContextClassLoader(),
HazelcastIndexedSessionRepository.class, MongoIndexedSessionRepository.class,
RedisIndexedSessionRepository.class))
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class, JdbcTemplateAutoConfiguration.class,
SessionAutoConfiguration.class))
SessionAutoConfiguration.class, JdbcSessionAutoConfiguration.class))
.withPropertyValues("spring.datasource.generate-unique-name=true");
}
@Test
void defaultConfig() {
this.contextRunner.run(this::validateDefaultConfig);
}
@Test
void jdbcTakesPrecedenceOverMongoAndHazelcast() {
this.contextRunner.withClassLoader(new FilteredClassLoader(RedisIndexedSessionRepository.class))
.run(this::validateDefaultConfig);
}
private void validateDefaultConfig(AssertableWebApplicationContext context) {
JdbcIndexedSessionRepository repository = validateSessionRepository(context,
JdbcIndexedSessionRepository.class);
assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval",
new ServerProperties().getServlet().getSession().getTimeout());
assertThat(repository).hasFieldOrPropertyWithValue("tableName", "SPRING_SESSION");
assertThat(repository).hasFieldOrPropertyWithValue("cleanupCron", "0 * * * * *");
assertThat(context.getBean(JdbcSessionProperties.class).getInitializeSchema())
.isEqualTo(DatabaseInitializationMode.EMBEDDED);
assertThat(context.getBean(JdbcOperations.class).queryForList("select * from SPRING_SESSION")).isEmpty();
}
@Test
void filterOrderCanBeCustomized() {
this.contextRunner.withPropertyValues("spring.session.servlet.filter-order=123").run((context) -> {
AbstractFilterRegistrationBean<?> registration = context.getBean(AbstractFilterRegistrationBean.class);
assertThat(registration.getOrder()).isEqualTo(123);
this.contextRunner.run((context) -> {
JdbcIndexedSessionRepository repository = validateSessionRepository(context,
JdbcIndexedSessionRepository.class);
assertThat(repository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval",
new ServerProperties().getServlet().getSession().getTimeout());
assertThat(repository).hasFieldOrPropertyWithValue("tableName", "SPRING_SESSION");
assertThat(repository).hasFieldOrPropertyWithValue("cleanupCron", "0 * * * * *");
assertThat(context.getBean(JdbcSessionProperties.class).getInitializeSchema())
.isEqualTo(DatabaseInitializationMode.EMBEDDED);
assertThat(context.getBean(JdbcOperations.class).queryForList("select * from SPRING_SESSION")).isEmpty();
});
}
@@ -136,7 +111,7 @@ class SessionAutoConfigurationJdbcTests extends AbstractSessionAutoConfiguration
@Test
void customTableName() {
this.contextRunner.withPropertyValues("spring.session.jdbc.table-name=FOO_BAR",
"spring.session.jdbc.schema=classpath:org/springframework/boot/autoconfigure/session/custom-schema-h2.sql")
"spring.session.jdbc.schema=classpath:org/springframework/boot/session/jdbc/autoconfigure/custom-schema-h2.sql")
.run((context) -> {
JdbcIndexedSessionRepository repository = validateSessionRepository(context,
JdbcIndexedSessionRepository.class);

View File

@@ -0,0 +1,32 @@
plugins {
id "java-library"
id "java-test-fixtures"
id "org.springframework.boot.auto-configuration"
id "org.springframework.boot.configuration-properties"
id "org.springframework.boot.deployed"
id "org.springframework.boot.optional-dependencies"
}
description = "Spring Boot Session"
dependencies {
api(project(":spring-boot-project:spring-boot"))
api("org.springframework.session:spring-session-core")
optional(project(":spring-boot-project:spring-boot-autoconfigure"))
optional("jakarta.servlet:jakarta.servlet-api")
optional("org.springframework.security:spring-security-web")
testFixturesImplementation(project(":spring-boot-project:spring-boot-test"))
testFixturesImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testFixturesImplementation(testFixtures(project(":spring-boot-project:spring-boot")))
testFixturesImplementation("io.projectreactor:reactor-core")
testImplementation(project(":spring-boot-project:spring-boot-test"))
testImplementation(project(":spring-boot-project:spring-boot-tomcat"))
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation(project(":spring-boot-project:spring-boot-webflux"))
testImplementation(testFixtures(project(":spring-boot-project:spring-boot")))
testRuntimeOnly("ch.qos.logback:logback-classic")
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
package org.springframework.boot.session.autoconfigure;
import org.springframework.session.web.http.DefaultCookieSerializer;
@@ -23,7 +23,7 @@ import org.springframework.session.web.http.DefaultCookieSerializer;
* {@link DefaultCookieSerializer} configuration.
*
* @author Vedran Pavic
* @since 2.3.0
* @since 4.0.0
*/
@FunctionalInterface
public interface DefaultCookieSerializerCustomizer {

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
package org.springframework.boot.session.autoconfigure;
import java.time.Duration;
@@ -37,9 +37,7 @@ import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.security.web.authentication.SpringSessionRememberMeServices;
import org.springframework.session.web.http.CookieHttpSessionIdResolver;
import org.springframework.session.web.http.CookieSerializer;
@@ -55,19 +53,9 @@ import org.springframework.session.web.http.HttpSessionIdResolver;
* @author Stephane Nicoll
* @author Vedran Pavic
* @author Weix Sun
* @since 1.4.0
* @since 4.0.0
*/
@AutoConfiguration(
beforeName = { "org.springframework.boot.webflux.autoconfigure.HttpHandlerAutoConfiguration",
"org.springframework.boot.webflux.autoconfigure.WebFluxAutoConfiguration" },
afterName = { "org.springframework.boot.hazelcast.autoconfigure.HazelcastAutoConfiguration",
"org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration",
"org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration",
"org.springframework.boot.data.mongodb.autoconfigure.MongoDataAutoConfiguration",
"org.springframework.boot.data.mongodb.autoconfigure.MongoReactiveDataAutoConfiguration",
"org.springframework.boot.data.redis.autoconfigure.RedisAutoConfiguration",
"org.springframework.boot.data.redis.autoconfigure.RedisReactiveAutoConfiguration",
"org.springframework.boot.autoconfigure.session.WebSessionIdResolverAutoConfiguration" })
@AutoConfiguration
@ConditionalOnClass(Session.class)
@ConditionalOnWebApplication
@EnableConfigurationProperties({ ServerProperties.class, SessionProperties.class })
@@ -109,22 +97,6 @@ public class SessionAutoConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(SessionRepository.class)
@Import({ RedisSessionConfiguration.class, JdbcSessionConfiguration.class, HazelcastSessionConfiguration.class,
MongoSessionConfiguration.class })
static class ServletSessionRepositoryConfiguration {
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.REACTIVE)
@ConditionalOnMissingBean(ReactiveSessionRepository.class)
@Import({ RedisReactiveSessionConfiguration.class, MongoReactiveSessionConfiguration.class })
static class ReactiveSessionConfiguration {
}
/**

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
package org.springframework.boot.session.autoconfigure;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
@@ -34,7 +34,7 @@ import org.springframework.session.web.http.SessionRepositoryFilter;
* @author Tommy Ludwig
* @author Stephane Nicoll
* @author Vedran Pavic
* @since 1.4.0
* @since 4.0.0
*/
@ConfigurationProperties("spring.session")
public class SessionProperties {
@@ -68,7 +68,6 @@ public class SessionProperties {
* {@code fallbackTimeout} is used.
* @param fallbackTimeout a fallback timeout value if the timeout isn't configured
* @return the session timeout
* @since 2.4.0
*/
public Duration determineTimeout(Supplier<Duration> fallbackTimeout) {
return (this.timeout != null) ? this.timeout : fallbackTimeout.get();

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
package org.springframework.boot.session.autoconfigure;
import java.util.EnumSet;
import java.util.stream.Collectors;

View File

@@ -17,4 +17,4 @@
/**
* Auto-configuration for Spring Session.
*/
package org.springframework.boot.autoconfigure.session;
package org.springframework.boot.session.autoconfigure;

View File

@@ -0,0 +1,12 @@
{
"properties": [
{
"name": "spring.session.servlet.filter-dispatcher-types",
"defaultValue": [
"async",
"error",
"request"
]
}
]
}

View File

@@ -0,0 +1 @@
org.springframework.boot.session.autoconfigure.SessionAutoConfiguration

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
package org.springframework.boot.session.autoconfigure;
import java.util.Collections;
@@ -26,22 +26,13 @@ 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.AbstractFilterRegistrationBean;
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;
@@ -63,39 +54,11 @@ import static org.mockito.Mockito.mock;
* @author Stephane Nicoll
* @author Vedran Pavic
*/
class SessionAutoConfigurationTests extends AbstractSessionAutoConfigurationTests {
class SessionAutoConfigurationTests {
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class));
@Test
void autoConfigurationDisabledIfNoImplementationMatches() {
this.contextRunner
.withClassLoader(new FilteredClassLoader(RedisIndexedSessionRepository.class,
HazelcastIndexedSessionRepository.class, JdbcIndexedSessionRepository.class,
MongoIndexedSessionRepository.class))
.run((context) -> assertThat(context).doesNotHaveBean(SessionRepository.class));
}
@Test
void backOffIfSessionRepositoryIsPresent() {
this.contextRunner.withUserConfiguration(SessionRepositoryConfiguration.class).run((context) -> {
MapSessionRepository repository = validateSessionRepository(context, MapSessionRepository.class);
assertThat(context).getBean("mySessionRepository").isSameAs(repository);
});
}
@Test
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
void filterIsRegisteredWithAsyncErrorAndRequestDispatcherTypes() {
this.contextRunner.withUserConfiguration(SessionRepositoryConfiguration.class).run((context) -> {
@@ -247,17 +210,6 @@ 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 {

View File

@@ -14,13 +14,19 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
package org.springframework.boot.session.autoconfigure;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.MapSessionRepository;
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
import org.springframework.session.web.http.DefaultCookieSerializer;
import static org.assertj.core.api.Assertions.assertThat;
@@ -32,7 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Vedran Pavic
*/
@ClassPathExclusions("spring-security-*")
class SessionAutoConfigurationWithoutSecurityTests extends AbstractSessionAutoConfigurationTests {
class SessionAutoConfigurationWithoutSecurityTests {
private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class));
@@ -45,4 +51,15 @@ class SessionAutoConfigurationWithoutSecurityTests extends AbstractSessionAutoCo
});
}
@Configuration(proxyBeanMethods = false)
@EnableSpringHttpSession
static class SessionRepositoryConfiguration {
@Bean
MapSessionRepository mySessionRepository() {
return new MapSessionRepository(Collections.emptyMap());
}
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
package org.springframework.boot.session.autoconfigure;
import java.time.Duration;
import java.util.function.Supplier;

View File

@@ -14,40 +14,51 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.session;
package org.springframework.boot.session.autoconfigure;
import java.util.Collections;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext;
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext;
import org.springframework.boot.web.server.reactive.MockReactiveWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.session.MapSessionRepository;
import org.springframework.session.ReactiveMapSessionRepository;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.SessionRepository;
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.session.config.annotation.web.server.EnableSpringWebSession;
import org.springframework.web.server.WebSession;
import org.springframework.web.server.session.WebSessionManager;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Shared test utilities for {@link SessionAutoConfiguration} tests.
* Base class for Spring Session auto-configuration tests when the backing store is
* reactive.
*
* @author Stephane Nicoll
* @author Weix Sun
* @author Andy Wilkinson
*/
public abstract class AbstractSessionAutoConfigurationTests {
public abstract class AbstractReactiveSessionAutoConfigurationTests {
private static final MockReactiveWebServerFactory mockReactiveWebServerFactory = new MockReactiveWebServerFactory();
protected ReactiveWebApplicationContextRunner contextRunner;
@Test
void backOffIfReactiveSessionRepositoryIsPresent() {
this.contextRunner.withUserConfiguration(ReactiveSessionRepositoryConfiguration.class).run((context) -> {
ReactiveMapSessionRepository repository = validateSessionRepository(context,
ReactiveMapSessionRepository.class);
assertThat(context).getBean("mySessionRepository").isSameAs(repository);
});
}
protected ContextConsumer<ReactiveWebApplicationContext> assertExchangeWithSession(
Consumer<MockServerWebExchange> exchange) {
return (context) -> {
@@ -61,15 +72,6 @@ public abstract class AbstractSessionAutoConfigurationTests {
};
}
protected <T extends SessionRepository<?>> T validateSessionRepository(AssertableWebApplicationContext context,
Class<T> type) {
assertThat(context).hasSingleBean(SessionRepositoryFilter.class);
assertThat(context).hasSingleBean(SessionRepository.class);
SessionRepository<?> repository = context.getBean(SessionRepository.class);
assertThat(repository).as("Wrong session repository type").isInstanceOf(type);
return type.cast(repository);
}
protected <T extends ReactiveSessionRepository<?>> T validateSessionRepository(
AssertableReactiveWebApplicationContext context, Class<T> type) {
assertThat(context).hasSingleBean(WebSessionManager.class);
@@ -79,19 +81,8 @@ public abstract class AbstractSessionAutoConfigurationTests {
return type.cast(repository);
}
@Configuration
@EnableSpringHttpSession
static class SessionRepositoryConfiguration {
@Bean
MapSessionRepository mySessionRepository() {
return new MapSessionRepository(Collections.emptyMap());
}
}
@Configuration(proxyBeanMethods = false)
static class Config {
protected static class ReactiveWebServerConfiguration {
@Bean
MockReactiveWebServerFactory mockReactiveWebServerFactory() {
@@ -100,4 +91,15 @@ public abstract class AbstractSessionAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
@EnableSpringWebSession
static class ReactiveSessionRepositoryConfiguration {
@Bean
ReactiveMapSessionRepository mySessionRepository() {
return new ReactiveMapSessionRepository(Collections.emptyMap());
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2012-2025 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.session.autoconfigure;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.MapSessionRepository;
import org.springframework.session.SessionRepository;
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
import org.springframework.session.web.http.SessionRepositoryFilter;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Base class for Spring Session auto-configuration tests when the backing store cannot be
* reactive.
*
* @author Stephane Nicoll
* @author Weix Sun
* @see AbstractReactiveSessionAutoConfigurationTests
*/
public abstract class AbstractSessionAutoConfigurationTests {
protected WebApplicationContextRunner contextRunner;
@Test
void backOffIfSessionRepositoryIsPresent() {
this.contextRunner.withUserConfiguration(SessionRepositoryConfiguration.class).run((context) -> {
MapSessionRepository repository = validateSessionRepository(context, MapSessionRepository.class);
assertThat(context).getBean("mySessionRepository").isSameAs(repository);
});
}
protected <T extends SessionRepository<?>> T validateSessionRepository(AssertableWebApplicationContext context,
Class<T> type) {
assertThat(context).hasSingleBean(SessionRepositoryFilter.class);
assertThat(context).hasSingleBean(SessionRepository.class);
SessionRepository<?> repository = context.getBean(SessionRepository.class);
assertThat(repository).as("Wrong session repository type").isInstanceOf(type);
return type.cast(repository);
}
@Configuration
@EnableSpringHttpSession
static class SessionRepositoryConfiguration {
@Bean
MapSessionRepository mySessionRepository() {
return new MapSessionRepository(Collections.emptyMap());
}
}
}

View File

@@ -5,13 +5,10 @@ plugins {
description = "Spring Boot Session smoke test"
dependencies {
implementation(project(":spring-boot-project:spring-boot-hazelcast"))
implementation(project(":spring-boot-project:spring-boot-session-hazelcast"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-actuator"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-security"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-web"))
implementation("org.springframework.session:spring-session-hazelcast") {
exclude group: "javax.annotation", module: "javax.annotation-api"
}
testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test"))
}

View File

@@ -5,11 +5,11 @@ plugins {
description = "Spring Boot Session JDBC smoke test"
dependencies {
implementation(project(":spring-boot-project:spring-boot-session-jdbc"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-actuator"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-security"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-web"))
runtimeOnly(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-jdbc"))
runtimeOnly("org.springframework.session:spring-session-jdbc")
runtimeOnly("com.h2database:h2")
testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test"))

View File

@@ -53,7 +53,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Madhura Bhave
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = { "server.servlet.session.timeout:2" })
properties = { "server.servlet.session.timeout:2", "debug=true" })
class SampleSessionJdbcApplicationTests {
private static final ClientHttpRequestFactorySettings DONT_FOLLOW_REDIRECTS = ClientHttpRequestFactorySettings

View File

@@ -12,9 +12,9 @@ dependencies {
dockerTestImplementation("org.testcontainers:junit-jupiter")
dockerTestImplementation("org.testcontainers:mongodb")
implementation(project(":spring-boot-project:spring-boot-session-data-mongodb"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-actuator"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-data-mongodb"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-security"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-web"))
implementation("org.springframework.session:spring-session-data-mongodb")
}

View File

@@ -12,9 +12,9 @@ dependencies {
dockerTestImplementation("com.redis:testcontainers-redis")
dockerTestImplementation("org.testcontainers:junit-jupiter")
implementation(project(":spring-boot-project:spring-boot-session-data-redis"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-actuator"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-data-redis"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-security"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-web"))
implementation("org.springframework.session:spring-session-data-redis")
}

View File

@@ -12,9 +12,9 @@ dependencies {
dockerTestImplementation("org.testcontainers:junit-jupiter")
dockerTestImplementation("org.testcontainers:mongodb")
implementation(project(":spring-boot-project:spring-boot-session-data-mongodb"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-security"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-webflux"))
runtimeOnly(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-data-mongodb-reactive"))
runtimeOnly("org.springframework.session:spring-session-data-mongodb")
}

View File

@@ -12,9 +12,9 @@ dependencies {
dockerTestImplementation("com.redis:testcontainers-redis")
dockerTestImplementation("org.testcontainers:junit-jupiter")
implementation(project(":spring-boot-project:spring-boot-session-data-redis"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-security"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-webflux"))
runtimeOnly(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-data-redis-reactive"))
runtimeOnly("org.springframework.session:spring-session-data-redis")
}