From 7b0abcbdb2b8055bee597ffdee6f6ed26b365901 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 11 Dec 2025 09:49:17 +0000 Subject: [PATCH] Decouple Session auto-configuration from ServerProperties Fixes gh-48493 --- .../build.gradle | 1 - .../SessionDataRedisAutoConfiguration.java | 30 ++--- module/spring-boot-session-jdbc/build.gradle | 2 +- .../JdbcSessionAutoConfiguration.java | 11 +- module/spring-boot-session/build.gradle | 3 +- .../SessionAutoConfiguration.java | 119 +++++++++++++++--- .../autoconfigure/SessionProperties.java | 2 + .../session/autoconfigure/SessionTimeout.java | 47 +++++++ .../SessionAutoConfigurationTests.java | 30 +++++ .../autoconfigure/SessionPropertiesTests.java | 5 +- 10 files changed, 198 insertions(+), 52 deletions(-) create mode 100644 module/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/SessionTimeout.java diff --git a/module/spring-boot-session-data-redis/build.gradle b/module/spring-boot-session-data-redis/build.gradle index 940fa78a692..0e8825a430e 100644 --- a/module/spring-boot-session-data-redis/build.gradle +++ b/module/spring-boot-session-data-redis/build.gradle @@ -31,7 +31,6 @@ dependencies { api("org.springframework.session:spring-session-data-redis") implementation(project(":module:spring-boot-data-redis")) - implementation(project(":module:spring-boot-web-server")) optional(project(":core:spring-boot-autoconfigure")) optional(project(":module:spring-boot-webflux")) diff --git a/module/spring-boot-session-data-redis/src/main/java/org/springframework/boot/session/data/redis/autoconfigure/SessionDataRedisAutoConfiguration.java b/module/spring-boot-session-data-redis/src/main/java/org/springframework/boot/session/data/redis/autoconfigure/SessionDataRedisAutoConfiguration.java index 49b81f2aa8a..29d025e83a3 100644 --- a/module/spring-boot-session-data-redis/src/main/java/org/springframework/boot/session/data/redis/autoconfigure/SessionDataRedisAutoConfiguration.java +++ b/module/spring-boot-session-data-redis/src/main/java/org/springframework/boot/session/data/redis/autoconfigure/SessionDataRedisAutoConfiguration.java @@ -31,7 +31,7 @@ import org.springframework.boot.data.redis.autoconfigure.DataRedisAutoConfigurat import org.springframework.boot.data.redis.autoconfigure.DataRedisReactiveAutoConfiguration; import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration; import org.springframework.boot.session.autoconfigure.SessionProperties; -import org.springframework.boot.web.server.autoconfigure.ServerProperties; +import org.springframework.boot.session.autoconfigure.SessionTimeout; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -74,7 +74,7 @@ import org.springframework.session.data.redis.config.annotation.web.server.Redis after = { DataRedisAutoConfiguration.class, DataRedisReactiveAutoConfiguration.class }, afterName = "org.springframework.boot.webflux.autoconfigure.WebSessionIdResolverAutoConfiguration") @ConditionalOnClass(Session.class) -@EnableConfigurationProperties({ SessionDataRedisProperties.class, ServerProperties.class, SessionProperties.class }) +@EnableConfigurationProperties(SessionDataRedisProperties.class) public final class SessionDataRedisAutoConfiguration { @Configuration(proxyBeanMethods = false) @@ -93,8 +93,7 @@ public final class SessionDataRedisAutoConfiguration { @Bean @Order(Ordered.HIGHEST_PRECEDENCE) SessionRepositoryCustomizer springBootSessionRepositoryCustomizer( - SessionProperties sessionProperties, SessionDataRedisProperties sessionDataRedisProperties, - ServerProperties serverProperties) { + SessionDataRedisProperties sessionDataRedisProperties, SessionTimeout sessionTimeout) { String cleanupCron = sessionDataRedisProperties.getCleanupCron(); if (cleanupCron != null) { throw new InvalidConfigurationPropertyValueException("spring.session.data.redis.cleanup-cron", @@ -103,9 +102,7 @@ public final class SessionDataRedisAutoConfiguration { } return (sessionRepository) -> { PropertyMapper map = PropertyMapper.get(); - map.from(sessionProperties - .determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout())) - .to(sessionRepository::setDefaultMaxInactiveInterval); + map.from(sessionTimeout::getTimeout).to(sessionRepository::setDefaultMaxInactiveInterval); map.from(sessionDataRedisProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace); map.from(sessionDataRedisProperties::getFlushMode).to(sessionRepository::setFlushMode); map.from(sessionDataRedisProperties::getSaveMode).to(sessionRepository::setSaveMode); @@ -132,12 +129,10 @@ public final class SessionDataRedisAutoConfiguration { @Order(Ordered.HIGHEST_PRECEDENCE) SessionRepositoryCustomizer springBootSessionRepositoryCustomizer( SessionProperties sessionProperties, SessionDataRedisProperties sessionDataRedisProperties, - ServerProperties serverProperties) { + SessionTimeout sessionTimeout) { return (sessionRepository) -> { PropertyMapper map = PropertyMapper.get(); - map.from(sessionProperties - .determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout())) - .to(sessionRepository::setDefaultMaxInactiveInterval); + map.from(sessionTimeout::getTimeout).to(sessionRepository::setDefaultMaxInactiveInterval); map.from(sessionDataRedisProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace); map.from(sessionDataRedisProperties::getFlushMode).to(sessionRepository::setFlushMode); map.from(sessionDataRedisProperties::getSaveMode).to(sessionRepository::setSaveMode); @@ -165,12 +160,10 @@ public final class SessionDataRedisAutoConfiguration { @Bean ReactiveSessionRepositoryCustomizer springBootSessionRepositoryCustomizer( SessionProperties sessionProperties, SessionDataRedisProperties sessionDataRedisProperties, - ServerProperties serverProperties) { + SessionTimeout sessionTimeout) { return (sessionRepository) -> { PropertyMapper map = PropertyMapper.get(); - map.from(sessionProperties - .determineTimeout(() -> serverProperties.getReactive().getSession().getTimeout())) - .to(sessionRepository::setDefaultMaxInactiveInterval); + map.from(sessionTimeout::getTimeout).to(sessionRepository::setDefaultMaxInactiveInterval); map.from(sessionDataRedisProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace); map.from(sessionDataRedisProperties::getSaveMode).to(sessionRepository::setSaveMode); }; @@ -195,13 +188,10 @@ public final class SessionDataRedisAutoConfiguration { @Bean ReactiveSessionRepositoryCustomizer springBootSessionRepositoryCustomizer( - SessionProperties sessionProperties, SessionDataRedisProperties sessionDataRedisProperties, - ServerProperties serverProperties) { + SessionDataRedisProperties sessionDataRedisProperties, SessionTimeout sessionTimeout) { return (sessionRepository) -> { PropertyMapper map = PropertyMapper.get(); - map.from(sessionProperties - .determineTimeout(() -> serverProperties.getReactive().getSession().getTimeout())) - .to(sessionRepository::setDefaultMaxInactiveInterval); + map.from(sessionTimeout::getTimeout).to(sessionRepository::setDefaultMaxInactiveInterval); map.from(sessionDataRedisProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace); map.from(sessionDataRedisProperties::getSaveMode).to(sessionRepository::setSaveMode); }; diff --git a/module/spring-boot-session-jdbc/build.gradle b/module/spring-boot-session-jdbc/build.gradle index 68002f1378d..4485270d31d 100644 --- a/module/spring-boot-session-jdbc/build.gradle +++ b/module/spring-boot-session-jdbc/build.gradle @@ -30,7 +30,6 @@ dependencies { api("org.springframework.session:spring-session-jdbc") implementation(project(":module:spring-boot-jdbc")) - implementation(project(":module:spring-boot-web-server")) optional(project(":core:spring-boot-autoconfigure")) @@ -38,6 +37,7 @@ dependencies { testImplementation(project(":test-support:spring-boot-test-support")) testImplementation(project(":module:spring-boot-flyway")) testImplementation(project(":module:spring-boot-liquibase")) + testImplementation(project(":module:spring-boot-web-server")) testImplementation(testFixtures(project(":module:spring-boot-session"))) testImplementation(testFixtures(project(":module:spring-boot-sql"))) testImplementation("jakarta.servlet:jakarta.servlet-api") diff --git a/module/spring-boot-session-jdbc/src/main/java/org/springframework/boot/session/jdbc/autoconfigure/JdbcSessionAutoConfiguration.java b/module/spring-boot-session-jdbc/src/main/java/org/springframework/boot/session/jdbc/autoconfigure/JdbcSessionAutoConfiguration.java index cd88ebdf48a..64800a2a202 100644 --- a/module/spring-boot-session-jdbc/src/main/java/org/springframework/boot/session/jdbc/autoconfigure/JdbcSessionAutoConfiguration.java +++ b/module/spring-boot-session-jdbc/src/main/java/org/springframework/boot/session/jdbc/autoconfigure/JdbcSessionAutoConfiguration.java @@ -29,10 +29,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat 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.session.autoconfigure.SessionTimeout; import org.springframework.boot.sql.autoconfigure.init.OnDatabaseInitializationCondition; import org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer; -import org.springframework.boot.web.server.autoconfigure.ServerProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Import; @@ -59,7 +58,7 @@ import org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessi @ConditionalOnClass({ Session.class, JdbcTemplate.class, JdbcIndexedSessionRepository.class }) @ConditionalOnMissingBean(SessionRepository.class) @ConditionalOnBean(DataSource.class) -@EnableConfigurationProperties({ JdbcSessionProperties.class, SessionProperties.class }) +@EnableConfigurationProperties(JdbcSessionProperties.class) @Import({ DatabaseInitializationDependencyConfigurer.class, JdbcHttpSessionConfiguration.class }) public final class JdbcSessionAutoConfiguration { @@ -76,12 +75,10 @@ public final class JdbcSessionAutoConfiguration { @Bean @Order(Ordered.HIGHEST_PRECEDENCE) SessionRepositoryCustomizer springBootSessionRepositoryCustomizer( - SessionProperties sessionProperties, JdbcSessionProperties jdbcSessionProperties, - ServerProperties serverProperties) { + JdbcSessionProperties jdbcSessionProperties, SessionTimeout sessionTimeout) { return (sessionRepository) -> { PropertyMapper map = PropertyMapper.get(); - map.from(sessionProperties.determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout())) - .to(sessionRepository::setDefaultMaxInactiveInterval); + map.from(sessionTimeout::getTimeout).to(sessionRepository::setDefaultMaxInactiveInterval); map.from(jdbcSessionProperties::getTableName).to(sessionRepository::setTableName); map.from(jdbcSessionProperties::getFlushMode).to(sessionRepository::setFlushMode); map.from(jdbcSessionProperties::getSaveMode).to(sessionRepository::setSaveMode); diff --git a/module/spring-boot-session/build.gradle b/module/spring-boot-session/build.gradle index 2827b513528..1d2ded17b29 100644 --- a/module/spring-boot-session/build.gradle +++ b/module/spring-boot-session/build.gradle @@ -29,10 +29,9 @@ dependencies { api(project(":core:spring-boot")) api("org.springframework.session:spring-session-core") - implementation(project(":module:spring-boot-web-server")) - optional(project(":core:spring-boot-autoconfigure")) optional(project(":module:spring-boot-actuator-autoconfigure")) + optional(project(":module:spring-boot-web-server")) optional("io.projectreactor:reactor-core") optional("jakarta.servlet:jakarta.servlet-api") optional("org.springframework.security:spring-security-web") diff --git a/module/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/SessionAutoConfiguration.java b/module/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/SessionAutoConfiguration.java index 0423a532fdf..7d0a9967779 100644 --- a/module/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/SessionAutoConfiguration.java +++ b/module/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/SessionAutoConfiguration.java @@ -17,6 +17,11 @@ package org.springframework.boot.session.autoconfigure; import java.time.Duration; +import java.util.function.Supplier; + +import jakarta.servlet.ServletContext; +import jakarta.servlet.SessionCookieConfig; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -25,6 +30,8 @@ import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; 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.ConditionalOnNotWarDeployment; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWarDeployment; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -43,6 +50,8 @@ import org.springframework.session.web.http.CookieHttpSessionIdResolver; import org.springframework.session.web.http.CookieSerializer; import org.springframework.session.web.http.DefaultCookieSerializer; import org.springframework.session.web.http.HttpSessionIdResolver; +import org.springframework.util.Assert; +import org.springframework.web.context.ServletContextAware; /** * {@link EnableAutoConfiguration Auto-configuration} for Spring Session. @@ -58,33 +67,19 @@ import org.springframework.session.web.http.HttpSessionIdResolver; @AutoConfiguration @ConditionalOnClass(Session.class) @ConditionalOnWebApplication -@EnableConfigurationProperties({ ServerProperties.class, SessionProperties.class }) +@EnableConfigurationProperties(SessionProperties.class) public final class SessionAutoConfiguration { + private static Duration determineTimeout(SessionProperties sessionProperties, Supplier fallbackTimeout) { + Duration timeout = sessionProperties.getTimeout(); + return (timeout != null) ? timeout : fallbackTimeout.get(); + } + @Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = Type.SERVLET) @Import(SessionRepositoryFilterConfiguration.class) static class ServletSessionConfiguration { - @Bean - @Conditional(DefaultCookieSerializerCondition.class) - DefaultCookieSerializer cookieSerializer(ServerProperties serverProperties, - ObjectProvider cookieSerializerCustomizers) { - Cookie cookie = serverProperties.getServlet().getSession().getCookie(); - DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer(); - PropertyMapper map = PropertyMapper.get(); - map.from(cookie::getName).to(cookieSerializer::setCookieName); - map.from(cookie::getDomain).to(cookieSerializer::setDomainName); - map.from(cookie::getPath).to(cookieSerializer::setCookiePath); - map.from(cookie::getHttpOnly).to(cookieSerializer::setUseHttpOnlyCookie); - map.from(cookie::getSecure).to(cookieSerializer::setUseSecureCookie); - map.from(cookie::getMaxAge).asInt(Duration::getSeconds).to(cookieSerializer::setCookieMaxAge); - map.from(cookie::getSameSite).as(SameSite::attributeValue).always().to(cookieSerializer::setSameSite); - map.from(cookie::getPartitioned).to(cookieSerializer::setPartitioned); - cookieSerializerCustomizers.orderedStream().forEach((customizer) -> customizer.customize(cookieSerializer)); - return cookieSerializer; - } - @Configuration(proxyBeanMethods = false) @ConditionalOnClass(RememberMeServices.class) static class RememberMeServicesConfiguration { @@ -97,6 +92,90 @@ public final class SessionAutoConfiguration { } + @ConditionalOnNotWarDeployment + @EnableConfigurationProperties(ServerProperties.class) + static class EmbeddedWebServerConfiguration { + + @Bean + SessionTimeout embeddedWebServerSessionTimeout(SessionProperties sessionProperties, + ServerProperties serverProperties) { + return () -> determineTimeout(sessionProperties, + serverProperties.getServlet().getSession()::getTimeout); + } + + @Bean + @Conditional(DefaultCookieSerializerCondition.class) + DefaultCookieSerializer cookieSerializer(ServerProperties serverProperties, + ObjectProvider cookieSerializerCustomizers) { + Cookie cookie = serverProperties.getServlet().getSession().getCookie(); + DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer(); + PropertyMapper map = PropertyMapper.get(); + map.from(cookie::getName).to(cookieSerializer::setCookieName); + map.from(cookie::getDomain).to(cookieSerializer::setDomainName); + map.from(cookie::getPath).to(cookieSerializer::setCookiePath); + map.from(cookie::getHttpOnly).to(cookieSerializer::setUseHttpOnlyCookie); + map.from(cookie::getSecure).to(cookieSerializer::setUseSecureCookie); + map.from(cookie::getMaxAge).asInt(Duration::getSeconds).to(cookieSerializer::setCookieMaxAge); + map.from(cookie::getSameSite).as(SameSite::attributeValue).always().to(cookieSerializer::setSameSite); + map.from(cookie::getPartitioned).to(cookieSerializer::setPartitioned); + cookieSerializerCustomizers.orderedStream() + .forEach((customizer) -> customizer.customize(cookieSerializer)); + return cookieSerializer; + } + + } + + @ConditionalOnWarDeployment + static class WarDepoymentConfiguration implements ServletContextAware { + + private @Nullable ServletContext servletContext; + + @Override + public void setServletContext(ServletContext servletContext) { + this.servletContext = servletContext; + } + + @Bean + SessionTimeout warDeplomentSessionTimeout(SessionProperties sessionProperties) { + return sessionProperties::getTimeout; + } + + @Bean + @Conditional(DefaultCookieSerializerCondition.class) + DefaultCookieSerializer cookieSerializer( + ObjectProvider cookieSerializerCustomizers) { + DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer(); + PropertyMapper map = PropertyMapper.get(); + Assert.notNull(this.servletContext, + "ServletContext is required for session configuration in a war deployment"); + SessionCookieConfig cookie = this.servletContext.getSessionCookieConfig(); + map.from(cookie::getName).to(cookieSerializer::setCookieName); + map.from(cookie::getDomain).to(cookieSerializer::setDomainName); + map.from(cookie::getPath).to(cookieSerializer::setCookiePath); + map.from(cookie::isHttpOnly).to(cookieSerializer::setUseHttpOnlyCookie); + map.from(cookie::isSecure).to(cookieSerializer::setUseSecureCookie); + map.from(cookie::getMaxAge).to(cookieSerializer::setCookieMaxAge); + map.from(cookie.getAttribute("SameSite")).always().to(cookieSerializer::setSameSite); + map.from(cookie.getAttribute("Partitioned")).as(Boolean::valueOf).to(cookieSerializer::setPartitioned); + cookieSerializerCustomizers.orderedStream() + .forEach((customizer) -> customizer.customize(cookieSerializer)); + return cookieSerializer; + } + + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnWebApplication(type = Type.REACTIVE) + static class ReactiveSessionConfiguration { + + @Bean + SessionTimeout embeddedWebServerSessionTimeout(SessionProperties sessionProperties, + ServerProperties serverProperties) { + return () -> determineTimeout(sessionProperties, serverProperties.getReactive().getSession()::getTimeout); + } + } /** diff --git a/module/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/SessionProperties.java b/module/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/SessionProperties.java index 9ff4447e126..0f7c500b41b 100644 --- a/module/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/SessionProperties.java +++ b/module/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/SessionProperties.java @@ -70,7 +70,9 @@ public class SessionProperties { * {@code fallbackTimeout} is used. * @param fallbackTimeout a fallback timeout value if the timeout isn't configured * @return the session timeout + * @deprecated since 4.0.1 for removal in 4.2.0 in favor of {@link SessionTimeout} */ + @Deprecated(since = "4.0.1", forRemoval = true) public Duration determineTimeout(Supplier fallbackTimeout) { return (this.timeout != null) ? this.timeout : fallbackTimeout.get(); } diff --git a/module/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/SessionTimeout.java b/module/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/SessionTimeout.java new file mode 100644 index 00000000000..c3163b2aafb --- /dev/null +++ b/module/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/SessionTimeout.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-present 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.time.Duration; + +import org.jspecify.annotations.Nullable; + +/** + * Provides access to the configured session timeout. The timeout is available + * irrespective of the deployment type: + *
    + *
  • a servlet web application + *
      + *
    • using an embedded web server + *
    • deployed as a war file + *
    + *
  • a reactive web application + *
+ * + * @author Andy Wilkinson + * @since 4.0.1 + */ +@FunctionalInterface +public interface SessionTimeout { + + /** + * Returns the session timeout or {@code null} if no timeout has been configured. + * @return the timeout or {@code null} + */ + @Nullable Duration getTimeout(); + +} diff --git a/module/spring-boot-session/src/test/java/org/springframework/boot/session/autoconfigure/SessionAutoConfigurationTests.java b/module/spring-boot-session/src/test/java/org/springframework/boot/session/autoconfigure/SessionAutoConfigurationTests.java index 341d4b7b0e0..46a0ee218f5 100644 --- a/module/spring-boot-session/src/test/java/org/springframework/boot/session/autoconfigure/SessionAutoConfigurationTests.java +++ b/module/spring-boot-session/src/test/java/org/springframework/boot/session/autoconfigure/SessionAutoConfigurationTests.java @@ -22,6 +22,7 @@ import jakarta.servlet.DispatcherType; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; +import jakarta.servlet.SessionCookieConfig; import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import org.mockito.InOrder; @@ -34,6 +35,7 @@ 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.mock.web.MockServletContext; import org.springframework.session.MapSessionRepository; import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession; import org.springframework.session.security.web.authentication.SpringSessionRememberMeServices; @@ -60,6 +62,7 @@ import static org.mockito.Mockito.mock; class SessionAutoConfigurationTests { private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() + .withInitializer((context) -> context.setServletContext(null)) // not a war .withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class)); @Test @@ -138,6 +141,33 @@ class SessionAutoConfigurationTests { }); } + @Test + void sessionCookieConfigIsAppliedToAutoConfiguredCookieSerializerInAWarDeployment() { + MockServletContext servletContext = new MockServletContext(); + SessionCookieConfig cookie = servletContext.getSessionCookieConfig(); + cookie.setName("sid"); + cookie.setDomain("spring"); + cookie.setPath("/test"); + cookie.setHttpOnly(false); + cookie.setSecure(false); + cookie.setMaxAge(10); + cookie.setAttribute("SameSite", "Strict"); + cookie.setAttribute("Partitioned", "true"); + this.contextRunner.withUserConfiguration(SessionRepositoryConfiguration.class) + .withInitializer((context) -> context.setServletContext(servletContext)) // war + .run((context) -> { + DefaultCookieSerializer cookieSerializer = context.getBean(DefaultCookieSerializer.class); + assertThat(cookieSerializer).hasFieldOrPropertyWithValue("cookieName", "sid"); + assertThat(cookieSerializer).hasFieldOrPropertyWithValue("domainName", "spring"); + assertThat(cookieSerializer).hasFieldOrPropertyWithValue("cookiePath", "/test"); + assertThat(cookieSerializer).hasFieldOrPropertyWithValue("useHttpOnlyCookie", false); + assertThat(cookieSerializer).hasFieldOrPropertyWithValue("useSecureCookie", false); + assertThat(cookieSerializer).hasFieldOrPropertyWithValue("cookieMaxAge", 10); + assertThat(cookieSerializer).hasFieldOrPropertyWithValue("sameSite", "Strict"); + assertThat(cookieSerializer).hasFieldOrPropertyWithValue("partitioned", true); + }); + } + @Test void sessionCookieSameSiteOmittedIsAppliedToAutoConfiguredCookieSerializer() { this.contextRunner.withUserConfiguration(SessionRepositoryConfiguration.class) diff --git a/module/spring-boot-session/src/test/java/org/springframework/boot/session/autoconfigure/SessionPropertiesTests.java b/module/spring-boot-session/src/test/java/org/springframework/boot/session/autoconfigure/SessionPropertiesTests.java index d67d97723f5..bbbf6ea242a 100644 --- a/module/spring-boot-session/src/test/java/org/springframework/boot/session/autoconfigure/SessionPropertiesTests.java +++ b/module/spring-boot-session/src/test/java/org/springframework/boot/session/autoconfigure/SessionPropertiesTests.java @@ -38,7 +38,8 @@ class SessionPropertiesTests { private final SessionProperties properties = new SessionProperties(); @Test - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "removal" }) + @Deprecated(since = "4.0.1", forRemoval = true) void determineTimeoutWithTimeoutIgnoreFallback() { this.properties.setTimeout(Duration.ofMinutes(1)); Supplier fallback = mock(Supplier.class); @@ -47,6 +48,8 @@ class SessionPropertiesTests { } @Test + @SuppressWarnings("removal") + @Deprecated(since = "4.0.1", forRemoval = true) void determineTimeoutWithNoTimeoutUseFallback() { this.properties.setTimeout(null); Duration fallback = Duration.ofMinutes(2);