Skip to content

Commit 7b0abcb

Browse files
committed
Decouple Session auto-configuration from ServerProperties
Fixes gh-48493
1 parent 2930849 commit 7b0abcb

File tree

10 files changed

+198
-52
lines changed

10 files changed

+198
-52
lines changed

module/spring-boot-session-data-redis/build.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ dependencies {
3131
api("org.springframework.session:spring-session-data-redis")
3232

3333
implementation(project(":module:spring-boot-data-redis"))
34-
implementation(project(":module:spring-boot-web-server"))
3534

3635
optional(project(":core:spring-boot-autoconfigure"))
3736
optional(project(":module:spring-boot-webflux"))

module/spring-boot-session-data-redis/src/main/java/org/springframework/boot/session/data/redis/autoconfigure/SessionDataRedisAutoConfiguration.java

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
import org.springframework.boot.data.redis.autoconfigure.DataRedisReactiveAutoConfiguration;
3232
import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration;
3333
import org.springframework.boot.session.autoconfigure.SessionProperties;
34-
import org.springframework.boot.web.server.autoconfigure.ServerProperties;
34+
import org.springframework.boot.session.autoconfigure.SessionTimeout;
3535
import org.springframework.context.annotation.Bean;
3636
import org.springframework.context.annotation.Configuration;
3737
import org.springframework.context.annotation.Import;
@@ -74,7 +74,7 @@
7474
after = { DataRedisAutoConfiguration.class, DataRedisReactiveAutoConfiguration.class },
7575
afterName = "org.springframework.boot.webflux.autoconfigure.WebSessionIdResolverAutoConfiguration")
7676
@ConditionalOnClass(Session.class)
77-
@EnableConfigurationProperties({ SessionDataRedisProperties.class, ServerProperties.class, SessionProperties.class })
77+
@EnableConfigurationProperties(SessionDataRedisProperties.class)
7878
public final class SessionDataRedisAutoConfiguration {
7979

8080
@Configuration(proxyBeanMethods = false)
@@ -93,8 +93,7 @@ static class DefaultRedisSessionConfiguration {
9393
@Bean
9494
@Order(Ordered.HIGHEST_PRECEDENCE)
9595
SessionRepositoryCustomizer<RedisSessionRepository> springBootSessionRepositoryCustomizer(
96-
SessionProperties sessionProperties, SessionDataRedisProperties sessionDataRedisProperties,
97-
ServerProperties serverProperties) {
96+
SessionDataRedisProperties sessionDataRedisProperties, SessionTimeout sessionTimeout) {
9897
String cleanupCron = sessionDataRedisProperties.getCleanupCron();
9998
if (cleanupCron != null) {
10099
throw new InvalidConfigurationPropertyValueException("spring.session.data.redis.cleanup-cron",
@@ -103,9 +102,7 @@ SessionRepositoryCustomizer<RedisSessionRepository> springBootSessionRepositoryC
103102
}
104103
return (sessionRepository) -> {
105104
PropertyMapper map = PropertyMapper.get();
106-
map.from(sessionProperties
107-
.determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout()))
108-
.to(sessionRepository::setDefaultMaxInactiveInterval);
105+
map.from(sessionTimeout::getTimeout).to(sessionRepository::setDefaultMaxInactiveInterval);
109106
map.from(sessionDataRedisProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace);
110107
map.from(sessionDataRedisProperties::getFlushMode).to(sessionRepository::setFlushMode);
111108
map.from(sessionDataRedisProperties::getSaveMode).to(sessionRepository::setSaveMode);
@@ -132,12 +129,10 @@ ConfigureRedisAction configureRedisAction(SessionDataRedisProperties sessionData
132129
@Order(Ordered.HIGHEST_PRECEDENCE)
133130
SessionRepositoryCustomizer<RedisIndexedSessionRepository> springBootSessionRepositoryCustomizer(
134131
SessionProperties sessionProperties, SessionDataRedisProperties sessionDataRedisProperties,
135-
ServerProperties serverProperties) {
132+
SessionTimeout sessionTimeout) {
136133
return (sessionRepository) -> {
137134
PropertyMapper map = PropertyMapper.get();
138-
map.from(sessionProperties
139-
.determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout()))
140-
.to(sessionRepository::setDefaultMaxInactiveInterval);
135+
map.from(sessionTimeout::getTimeout).to(sessionRepository::setDefaultMaxInactiveInterval);
141136
map.from(sessionDataRedisProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace);
142137
map.from(sessionDataRedisProperties::getFlushMode).to(sessionRepository::setFlushMode);
143138
map.from(sessionDataRedisProperties::getSaveMode).to(sessionRepository::setSaveMode);
@@ -165,12 +160,10 @@ static class DefaultRedisSessionConfiguration {
165160
@Bean
166161
ReactiveSessionRepositoryCustomizer<ReactiveRedisSessionRepository> springBootSessionRepositoryCustomizer(
167162
SessionProperties sessionProperties, SessionDataRedisProperties sessionDataRedisProperties,
168-
ServerProperties serverProperties) {
163+
SessionTimeout sessionTimeout) {
169164
return (sessionRepository) -> {
170165
PropertyMapper map = PropertyMapper.get();
171-
map.from(sessionProperties
172-
.determineTimeout(() -> serverProperties.getReactive().getSession().getTimeout()))
173-
.to(sessionRepository::setDefaultMaxInactiveInterval);
166+
map.from(sessionTimeout::getTimeout).to(sessionRepository::setDefaultMaxInactiveInterval);
174167
map.from(sessionDataRedisProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace);
175168
map.from(sessionDataRedisProperties::getSaveMode).to(sessionRepository::setSaveMode);
176169
};
@@ -195,13 +188,10 @@ ConfigureReactiveRedisAction configureReactiveRedisAction(
195188

196189
@Bean
197190
ReactiveSessionRepositoryCustomizer<ReactiveRedisIndexedSessionRepository> springBootSessionRepositoryCustomizer(
198-
SessionProperties sessionProperties, SessionDataRedisProperties sessionDataRedisProperties,
199-
ServerProperties serverProperties) {
191+
SessionDataRedisProperties sessionDataRedisProperties, SessionTimeout sessionTimeout) {
200192
return (sessionRepository) -> {
201193
PropertyMapper map = PropertyMapper.get();
202-
map.from(sessionProperties
203-
.determineTimeout(() -> serverProperties.getReactive().getSession().getTimeout()))
204-
.to(sessionRepository::setDefaultMaxInactiveInterval);
194+
map.from(sessionTimeout::getTimeout).to(sessionRepository::setDefaultMaxInactiveInterval);
205195
map.from(sessionDataRedisProperties::getNamespace).to(sessionRepository::setRedisKeyNamespace);
206196
map.from(sessionDataRedisProperties::getSaveMode).to(sessionRepository::setSaveMode);
207197
};

module/spring-boot-session-jdbc/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ dependencies {
3030
api("org.springframework.session:spring-session-jdbc")
3131

3232
implementation(project(":module:spring-boot-jdbc"))
33-
implementation(project(":module:spring-boot-web-server"))
3433

3534
optional(project(":core:spring-boot-autoconfigure"))
3635

3736
testImplementation(project(":core:spring-boot-test"))
3837
testImplementation(project(":test-support:spring-boot-test-support"))
3938
testImplementation(project(":module:spring-boot-flyway"))
4039
testImplementation(project(":module:spring-boot-liquibase"))
40+
testImplementation(project(":module:spring-boot-web-server"))
4141
testImplementation(testFixtures(project(":module:spring-boot-session")))
4242
testImplementation(testFixtures(project(":module:spring-boot-sql")))
4343
testImplementation("jakarta.servlet:jakarta.servlet-api")

module/spring-boot-session-jdbc/src/main/java/org/springframework/boot/session/jdbc/autoconfigure/JdbcSessionAutoConfiguration.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,9 @@
2929
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3030
import org.springframework.boot.context.properties.PropertyMapper;
3131
import org.springframework.boot.session.autoconfigure.SessionAutoConfiguration;
32-
import org.springframework.boot.session.autoconfigure.SessionProperties;
32+
import org.springframework.boot.session.autoconfigure.SessionTimeout;
3333
import org.springframework.boot.sql.autoconfigure.init.OnDatabaseInitializationCondition;
3434
import org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer;
35-
import org.springframework.boot.web.server.autoconfigure.ServerProperties;
3635
import org.springframework.context.annotation.Bean;
3736
import org.springframework.context.annotation.Conditional;
3837
import org.springframework.context.annotation.Import;
@@ -59,7 +58,7 @@
5958
@ConditionalOnClass({ Session.class, JdbcTemplate.class, JdbcIndexedSessionRepository.class })
6059
@ConditionalOnMissingBean(SessionRepository.class)
6160
@ConditionalOnBean(DataSource.class)
62-
@EnableConfigurationProperties({ JdbcSessionProperties.class, SessionProperties.class })
61+
@EnableConfigurationProperties(JdbcSessionProperties.class)
6362
@Import({ DatabaseInitializationDependencyConfigurer.class, JdbcHttpSessionConfiguration.class })
6463
public final class JdbcSessionAutoConfiguration {
6564

@@ -76,12 +75,10 @@ JdbcSessionDataSourceScriptDatabaseInitializer jdbcSessionDataSourceScriptDataba
7675
@Bean
7776
@Order(Ordered.HIGHEST_PRECEDENCE)
7877
SessionRepositoryCustomizer<JdbcIndexedSessionRepository> springBootSessionRepositoryCustomizer(
79-
SessionProperties sessionProperties, JdbcSessionProperties jdbcSessionProperties,
80-
ServerProperties serverProperties) {
78+
JdbcSessionProperties jdbcSessionProperties, SessionTimeout sessionTimeout) {
8179
return (sessionRepository) -> {
8280
PropertyMapper map = PropertyMapper.get();
83-
map.from(sessionProperties.determineTimeout(() -> serverProperties.getServlet().getSession().getTimeout()))
84-
.to(sessionRepository::setDefaultMaxInactiveInterval);
81+
map.from(sessionTimeout::getTimeout).to(sessionRepository::setDefaultMaxInactiveInterval);
8582
map.from(jdbcSessionProperties::getTableName).to(sessionRepository::setTableName);
8683
map.from(jdbcSessionProperties::getFlushMode).to(sessionRepository::setFlushMode);
8784
map.from(jdbcSessionProperties::getSaveMode).to(sessionRepository::setSaveMode);

module/spring-boot-session/build.gradle

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,9 @@ dependencies {
2929
api(project(":core:spring-boot"))
3030
api("org.springframework.session:spring-session-core")
3131

32-
implementation(project(":module:spring-boot-web-server"))
33-
3432
optional(project(":core:spring-boot-autoconfigure"))
3533
optional(project(":module:spring-boot-actuator-autoconfigure"))
34+
optional(project(":module:spring-boot-web-server"))
3635
optional("io.projectreactor:reactor-core")
3736
optional("jakarta.servlet:jakarta.servlet-api")
3837
optional("org.springframework.security:spring-security-web")

module/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/SessionAutoConfiguration.java

Lines changed: 99 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
package org.springframework.boot.session.autoconfigure;
1818

1919
import java.time.Duration;
20+
import java.util.function.Supplier;
21+
22+
import jakarta.servlet.ServletContext;
23+
import jakarta.servlet.SessionCookieConfig;
24+
import org.jspecify.annotations.Nullable;
2025

2126
import org.springframework.beans.factory.ObjectProvider;
2227
import org.springframework.boot.autoconfigure.AutoConfiguration;
@@ -25,6 +30,8 @@
2530
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2631
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2732
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
33+
import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWarDeployment;
34+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWarDeployment;
2835
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
2936
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
3037
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -43,6 +50,8 @@
4350
import org.springframework.session.web.http.CookieSerializer;
4451
import org.springframework.session.web.http.DefaultCookieSerializer;
4552
import org.springframework.session.web.http.HttpSessionIdResolver;
53+
import org.springframework.util.Assert;
54+
import org.springframework.web.context.ServletContextAware;
4655

4756
/**
4857
* {@link EnableAutoConfiguration Auto-configuration} for Spring Session.
@@ -58,33 +67,19 @@
5867
@AutoConfiguration
5968
@ConditionalOnClass(Session.class)
6069
@ConditionalOnWebApplication
61-
@EnableConfigurationProperties({ ServerProperties.class, SessionProperties.class })
70+
@EnableConfigurationProperties(SessionProperties.class)
6271
public final class SessionAutoConfiguration {
6372

73+
private static Duration determineTimeout(SessionProperties sessionProperties, Supplier<Duration> fallbackTimeout) {
74+
Duration timeout = sessionProperties.getTimeout();
75+
return (timeout != null) ? timeout : fallbackTimeout.get();
76+
}
77+
6478
@Configuration(proxyBeanMethods = false)
6579
@ConditionalOnWebApplication(type = Type.SERVLET)
6680
@Import(SessionRepositoryFilterConfiguration.class)
6781
static class ServletSessionConfiguration {
6882

69-
@Bean
70-
@Conditional(DefaultCookieSerializerCondition.class)
71-
DefaultCookieSerializer cookieSerializer(ServerProperties serverProperties,
72-
ObjectProvider<DefaultCookieSerializerCustomizer> cookieSerializerCustomizers) {
73-
Cookie cookie = serverProperties.getServlet().getSession().getCookie();
74-
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
75-
PropertyMapper map = PropertyMapper.get();
76-
map.from(cookie::getName).to(cookieSerializer::setCookieName);
77-
map.from(cookie::getDomain).to(cookieSerializer::setDomainName);
78-
map.from(cookie::getPath).to(cookieSerializer::setCookiePath);
79-
map.from(cookie::getHttpOnly).to(cookieSerializer::setUseHttpOnlyCookie);
80-
map.from(cookie::getSecure).to(cookieSerializer::setUseSecureCookie);
81-
map.from(cookie::getMaxAge).asInt(Duration::getSeconds).to(cookieSerializer::setCookieMaxAge);
82-
map.from(cookie::getSameSite).as(SameSite::attributeValue).always().to(cookieSerializer::setSameSite);
83-
map.from(cookie::getPartitioned).to(cookieSerializer::setPartitioned);
84-
cookieSerializerCustomizers.orderedStream().forEach((customizer) -> customizer.customize(cookieSerializer));
85-
return cookieSerializer;
86-
}
87-
8883
@Configuration(proxyBeanMethods = false)
8984
@ConditionalOnClass(RememberMeServices.class)
9085
static class RememberMeServicesConfiguration {
@@ -97,6 +92,90 @@ DefaultCookieSerializerCustomizer rememberMeServicesCookieSerializerCustomizer()
9792

9893
}
9994

95+
@ConditionalOnNotWarDeployment
96+
@EnableConfigurationProperties(ServerProperties.class)
97+
static class EmbeddedWebServerConfiguration {
98+
99+
@Bean
100+
SessionTimeout embeddedWebServerSessionTimeout(SessionProperties sessionProperties,
101+
ServerProperties serverProperties) {
102+
return () -> determineTimeout(sessionProperties,
103+
serverProperties.getServlet().getSession()::getTimeout);
104+
}
105+
106+
@Bean
107+
@Conditional(DefaultCookieSerializerCondition.class)
108+
DefaultCookieSerializer cookieSerializer(ServerProperties serverProperties,
109+
ObjectProvider<DefaultCookieSerializerCustomizer> cookieSerializerCustomizers) {
110+
Cookie cookie = serverProperties.getServlet().getSession().getCookie();
111+
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
112+
PropertyMapper map = PropertyMapper.get();
113+
map.from(cookie::getName).to(cookieSerializer::setCookieName);
114+
map.from(cookie::getDomain).to(cookieSerializer::setDomainName);
115+
map.from(cookie::getPath).to(cookieSerializer::setCookiePath);
116+
map.from(cookie::getHttpOnly).to(cookieSerializer::setUseHttpOnlyCookie);
117+
map.from(cookie::getSecure).to(cookieSerializer::setUseSecureCookie);
118+
map.from(cookie::getMaxAge).asInt(Duration::getSeconds).to(cookieSerializer::setCookieMaxAge);
119+
map.from(cookie::getSameSite).as(SameSite::attributeValue).always().to(cookieSerializer::setSameSite);
120+
map.from(cookie::getPartitioned).to(cookieSerializer::setPartitioned);
121+
cookieSerializerCustomizers.orderedStream()
122+
.forEach((customizer) -> customizer.customize(cookieSerializer));
123+
return cookieSerializer;
124+
}
125+
126+
}
127+
128+
@ConditionalOnWarDeployment
129+
static class WarDepoymentConfiguration implements ServletContextAware {
130+
131+
private @Nullable ServletContext servletContext;
132+
133+
@Override
134+
public void setServletContext(ServletContext servletContext) {
135+
this.servletContext = servletContext;
136+
}
137+
138+
@Bean
139+
SessionTimeout warDeplomentSessionTimeout(SessionProperties sessionProperties) {
140+
return sessionProperties::getTimeout;
141+
}
142+
143+
@Bean
144+
@Conditional(DefaultCookieSerializerCondition.class)
145+
DefaultCookieSerializer cookieSerializer(
146+
ObjectProvider<DefaultCookieSerializerCustomizer> cookieSerializerCustomizers) {
147+
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
148+
PropertyMapper map = PropertyMapper.get();
149+
Assert.notNull(this.servletContext,
150+
"ServletContext is required for session configuration in a war deployment");
151+
SessionCookieConfig cookie = this.servletContext.getSessionCookieConfig();
152+
map.from(cookie::getName).to(cookieSerializer::setCookieName);
153+
map.from(cookie::getDomain).to(cookieSerializer::setDomainName);
154+
map.from(cookie::getPath).to(cookieSerializer::setCookiePath);
155+
map.from(cookie::isHttpOnly).to(cookieSerializer::setUseHttpOnlyCookie);
156+
map.from(cookie::isSecure).to(cookieSerializer::setUseSecureCookie);
157+
map.from(cookie::getMaxAge).to(cookieSerializer::setCookieMaxAge);
158+
map.from(cookie.getAttribute("SameSite")).always().to(cookieSerializer::setSameSite);
159+
map.from(cookie.getAttribute("Partitioned")).as(Boolean::valueOf).to(cookieSerializer::setPartitioned);
160+
cookieSerializerCustomizers.orderedStream()
161+
.forEach((customizer) -> customizer.customize(cookieSerializer));
162+
return cookieSerializer;
163+
}
164+
165+
}
166+
167+
}
168+
169+
@Configuration(proxyBeanMethods = false)
170+
@ConditionalOnWebApplication(type = Type.REACTIVE)
171+
static class ReactiveSessionConfiguration {
172+
173+
@Bean
174+
SessionTimeout embeddedWebServerSessionTimeout(SessionProperties sessionProperties,
175+
ServerProperties serverProperties) {
176+
return () -> determineTimeout(sessionProperties, serverProperties.getReactive().getSession()::getTimeout);
177+
}
178+
100179
}
101180

102181
/**

module/spring-boot-session/src/main/java/org/springframework/boot/session/autoconfigure/SessionProperties.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ public void setServlet(Servlet servlet) {
7070
* {@code fallbackTimeout} is used.
7171
* @param fallbackTimeout a fallback timeout value if the timeout isn't configured
7272
* @return the session timeout
73+
* @deprecated since 4.0.1 for removal in 4.2.0 in favor of {@link SessionTimeout}
7374
*/
75+
@Deprecated(since = "4.0.1", forRemoval = true)
7476
public Duration determineTimeout(Supplier<Duration> fallbackTimeout) {
7577
return (this.timeout != null) ? this.timeout : fallbackTimeout.get();
7678
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.session.autoconfigure;
18+
19+
import java.time.Duration;
20+
21+
import org.jspecify.annotations.Nullable;
22+
23+
/**
24+
* Provides access to the configured session timeout. The timeout is available
25+
* irrespective of the deployment type:
26+
* <ul>
27+
* <li>a servlet web application
28+
* <ul>
29+
* <li>using an embedded web server
30+
* <li>deployed as a war file
31+
* </ul>
32+
* <li>a reactive web application
33+
* </ul>
34+
*
35+
* @author Andy Wilkinson
36+
* @since 4.0.1
37+
*/
38+
@FunctionalInterface
39+
public interface SessionTimeout {
40+
41+
/**
42+
* Returns the session timeout or {@code null} if no timeout has been configured.
43+
* @return the timeout or {@code null}
44+
*/
45+
@Nullable Duration getTimeout();
46+
47+
}

0 commit comments

Comments
 (0)