Skip to content

Commit a2a802a

Browse files
weixsunphilwebb
authored andcommitted
Add more session properties for reactive web servers
Expand the session properties supported by reactive web servers to include `timeout` support and additional `cookie` properties. See gh-26714
1 parent 3729c49 commit a2a802a

File tree

10 files changed

+394
-15
lines changed

10 files changed

+394
-15
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/MongoReactiveSessionConfiguration.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
2222
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2323
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2424
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
25+
import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties;
2526
import org.springframework.boot.context.properties.EnableConfigurationProperties;
2627
import org.springframework.context.annotation.Conditional;
2728
import org.springframework.context.annotation.Configuration;
@@ -47,8 +48,9 @@ class MongoReactiveSessionConfiguration {
4748
static class SpringBootReactiveMongoWebSessionConfiguration extends ReactiveMongoWebSessionConfiguration {
4849

4950
@Autowired
50-
void customize(SessionProperties sessionProperties, MongoSessionProperties mongoSessionProperties) {
51-
Duration timeout = sessionProperties.getTimeout();
51+
void customize(SessionProperties sessionProperties, MongoSessionProperties mongoSessionProperties,
52+
WebFluxProperties webFluxProperties) {
53+
Duration timeout = sessionProperties.determineTimeout(() -> webFluxProperties.getSession().getTimeout());
5254
if (timeout != null) {
5355
setMaxInactiveIntervalInSeconds((int) timeout.getSeconds());
5456
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/session/RedisReactiveSessionConfiguration.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
2222
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2323
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2424
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
25+
import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties;
2526
import org.springframework.boot.context.properties.EnableConfigurationProperties;
2627
import org.springframework.context.annotation.Conditional;
2728
import org.springframework.context.annotation.Configuration;
@@ -47,8 +48,9 @@ class RedisReactiveSessionConfiguration {
4748
static class SpringBootRedisWebSessionConfiguration extends RedisWebSessionConfiguration {
4849

4950
@Autowired
50-
void customize(SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties) {
51-
Duration timeout = sessionProperties.getTimeout();
51+
void customize(SessionProperties sessionProperties, RedisSessionProperties redisSessionProperties,
52+
WebFluxProperties webFluxProperties) {
53+
Duration timeout = sessionProperties.determineTimeout(() -> webFluxProperties.getSession().getTimeout());
5254
if (timeout != null) {
5355
setMaxInactiveIntervalInSeconds((int) timeout.getSeconds());
5456
}

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
3232
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3333
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
34+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
3435
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
3536
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
3637
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
@@ -43,6 +44,8 @@
4344
import org.springframework.boot.autoconfigure.web.ServerProperties;
4445
import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration;
4546
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
47+
import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties;
48+
import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties.SameSite;
4649
import org.springframework.boot.context.properties.EnableConfigurationProperties;
4750
import org.springframework.boot.context.properties.PropertyMapper;
4851
import org.springframework.boot.web.servlet.server.Session.Cookie;
@@ -62,6 +65,8 @@
6265
import org.springframework.session.web.http.CookieSerializer;
6366
import org.springframework.session.web.http.DefaultCookieSerializer;
6467
import org.springframework.session.web.http.HttpSessionIdResolver;
68+
import org.springframework.web.server.session.CookieWebSessionIdResolver;
69+
import org.springframework.web.server.session.WebSessionIdResolver;
6570

6671
/**
6772
* {@link EnableAutoConfiguration Auto-configuration} for Spring Session.
@@ -71,12 +76,13 @@
7176
* @author Eddú Meléndez
7277
* @author Stephane Nicoll
7378
* @author Vedran Pavic
79+
* @author Weix Sun
7480
* @since 1.4.0
7581
*/
7682
@Configuration(proxyBeanMethods = false)
7783
@ConditionalOnClass(Session.class)
7884
@ConditionalOnWebApplication
79-
@EnableConfigurationProperties({ ServerProperties.class, SessionProperties.class })
85+
@EnableConfigurationProperties({ ServerProperties.class, SessionProperties.class, WebFluxProperties.class })
8086
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HazelcastAutoConfiguration.class,
8187
JdbcTemplateAutoConfiguration.class, MongoDataAutoConfiguration.class, MongoReactiveDataAutoConfiguration.class,
8288
RedisAutoConfiguration.class, RedisReactiveAutoConfiguration.class })
@@ -132,6 +138,29 @@ static class ServletSessionRepositoryConfiguration {
132138
@Import(ReactiveSessionRepositoryValidator.class)
133139
static class ReactiveSessionConfiguration {
134140

141+
private static final String WEB_SESSION_ID_RESOLVER_BEAN_NAME = "webSessionIdResolver";
142+
143+
@Bean
144+
@ConditionalOnMissingClass(WEB_SESSION_ID_RESOLVER_BEAN_NAME)
145+
WebSessionIdResolver webSessionIdResolver(WebFluxProperties webFluxProperties) {
146+
final WebFluxProperties.Cookie cookie = webFluxProperties.getSession().getCookie();
147+
CookieWebSessionIdResolver webSessionIdResolver = new CookieWebSessionIdResolver();
148+
webSessionIdResolver.setCookieName(cookie.getName());
149+
webSessionIdResolver.setCookieMaxAge(cookie.getMaxAge());
150+
webSessionIdResolver.addCookieInitializer((cookieBuilder) -> applyOtherProperties(cookie, cookieBuilder));
151+
return webSessionIdResolver;
152+
}
153+
154+
private void applyOtherProperties(WebFluxProperties.Cookie cookie,
155+
org.springframework.http.ResponseCookie.ResponseCookieBuilder cookieBuilder) {
156+
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
157+
map.from(cookie::getDomain).to(cookieBuilder::domain);
158+
map.from(cookie::getPath).to(cookieBuilder::path);
159+
map.from(cookie::getHttpOnly).to(cookieBuilder::httpOnly);
160+
map.from(cookie::getSecure).to(cookieBuilder::secure);
161+
map.from(cookie::getSameSite).as(SameSite::attribute).to(cookieBuilder::sameSite);
162+
}
163+
135164
@Configuration(proxyBeanMethods = false)
136165
@ConditionalOnMissingBean(ReactiveSessionRepository.class)
137166
@Import({ ReactiveSessionRepositoryImplementationValidator.class,

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.apache.commons.logging.Log;
2323
import org.apache.commons.logging.LogFactory;
24+
import reactor.core.publisher.Mono;
2425

2526
import org.springframework.beans.factory.ListableBeanFactory;
2627
import org.springframework.beans.factory.ObjectProvider;
@@ -40,8 +41,11 @@
4041
import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
4142
import org.springframework.boot.autoconfigure.web.format.DateTimeFormatters;
4243
import org.springframework.boot.autoconfigure.web.format.WebConversionService;
44+
import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties.Cookie;
4345
import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties.Format;
46+
import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties.SameSite;
4447
import org.springframework.boot.context.properties.EnableConfigurationProperties;
48+
import org.springframework.boot.context.properties.PropertyMapper;
4549
import org.springframework.boot.convert.ApplicationConversionService;
4650
import org.springframework.boot.web.codec.CodecCustomizer;
4751
import org.springframework.boot.web.reactive.filter.OrderedHiddenHttpMethodFilter;
@@ -72,12 +76,14 @@
7276
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter;
7377
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
7478
import org.springframework.web.reactive.result.view.ViewResolver;
79+
import org.springframework.web.server.WebSession;
7580
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
7681
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
7782
import org.springframework.web.server.i18n.FixedLocaleContextResolver;
7883
import org.springframework.web.server.i18n.LocaleContextResolver;
7984
import org.springframework.web.server.session.CookieWebSessionIdResolver;
8085
import org.springframework.web.server.session.DefaultWebSessionManager;
86+
import org.springframework.web.server.session.InMemoryWebSessionStore;
8187
import org.springframework.web.server.session.WebSessionIdResolver;
8288
import org.springframework.web.server.session.WebSessionManager;
8389

@@ -92,6 +98,7 @@
9298
* @author Eddú Meléndez
9399
* @author Artsiom Yudovin
94100
* @author Chris Bono
101+
* @author Weix Sun
95102
* @since 2.0.0
96103
*/
97104
@Configuration(proxyBeanMethods = false)
@@ -304,19 +311,51 @@ public LocaleContextResolver localeContextResolver() {
304311
@ConditionalOnMissingBean(name = WebHttpHandlerBuilder.WEB_SESSION_MANAGER_BEAN_NAME)
305312
public WebSessionManager webSessionManager(ObjectProvider<WebSessionIdResolver> webSessionIdResolver) {
306313
DefaultWebSessionManager webSessionManager = new DefaultWebSessionManager();
314+
DefaultInMemoryWebSessionStore sessionStore = new DefaultInMemoryWebSessionStore(
315+
this.webFluxProperties.getSession().getTimeout());
316+
webSessionManager.setSessionStore(sessionStore);
307317
webSessionManager.setSessionIdResolver(webSessionIdResolver.getIfAvailable(cookieWebSessionIdResolver()));
308318
return webSessionManager;
309319
}
310320

311321
private Supplier<WebSessionIdResolver> cookieWebSessionIdResolver() {
312322
return () -> {
313323
CookieWebSessionIdResolver webSessionIdResolver = new CookieWebSessionIdResolver();
314-
webSessionIdResolver.addCookieInitializer((cookie) -> cookie
315-
.sameSite(this.webFluxProperties.getSession().getCookie().getSameSite().attribute()));
324+
webSessionIdResolver.setCookieName(this.webFluxProperties.getSession().getCookie().getName());
325+
webSessionIdResolver.addCookieInitializer((cookie) -> applyOtherProperties(cookie));
316326
return webSessionIdResolver;
317327
};
318328
}
319329

330+
private void applyOtherProperties(org.springframework.http.ResponseCookie.ResponseCookieBuilder cookieBuilder) {
331+
Cookie cookie = this.webFluxProperties.getSession().getCookie();
332+
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
333+
map.from(cookie::getDomain).to(cookieBuilder::domain);
334+
map.from(cookie::getPath).to(cookieBuilder::path);
335+
map.from(cookie::getMaxAge).to(cookieBuilder::maxAge);
336+
map.from(cookie::getHttpOnly).to(cookieBuilder::httpOnly);
337+
map.from(cookie::getSecure).to(cookieBuilder::secure);
338+
map.from(cookie::getSameSite).as(SameSite::attribute).to(cookieBuilder::sameSite);
339+
}
340+
341+
static final class DefaultInMemoryWebSessionStore extends InMemoryWebSessionStore {
342+
343+
private final Duration timeout;
344+
345+
private DefaultInMemoryWebSessionStore(Duration timeout) {
346+
this.timeout = timeout;
347+
}
348+
349+
@Override
350+
public Mono<WebSession> createWebSession() {
351+
return super.createWebSession().flatMap((inMemoryWebSession) -> {
352+
inMemoryWebSession.setMaxIdleTime(this.timeout);
353+
return Mono.just(inMemoryWebSession);
354+
});
355+
}
356+
357+
}
358+
320359
}
321360

322361
@Configuration(proxyBeanMethods = false)

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxProperties.java

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616

1717
package org.springframework.boot.autoconfigure.web.reactive;
1818

19+
import java.time.Duration;
20+
import java.time.temporal.ChronoUnit;
21+
1922
import org.springframework.boot.context.properties.ConfigurationProperties;
23+
import org.springframework.boot.convert.DurationUnit;
2024
import org.springframework.util.StringUtils;
2125

2226
/**
@@ -124,21 +128,145 @@ public void setDateTime(String dateTime) {
124128

125129
public static class Session {
126130

131+
/**
132+
* Session timeout. If a duration suffix is not specified, seconds will be used.
133+
*/
134+
@DurationUnit(ChronoUnit.SECONDS)
135+
private Duration timeout = Duration.ofMinutes(30);
136+
127137
private final Cookie cookie = new Cookie();
128138

129139
public Cookie getCookie() {
130140
return this.cookie;
131141
}
132142

143+
public Duration getTimeout() {
144+
return this.timeout;
145+
}
146+
147+
public void setTimeout(Duration timeout) {
148+
this.timeout = timeout;
149+
}
150+
133151
}
134152

135153
public static class Cookie {
136154

155+
private static final String COOKIE_NAME = "SESSION";
156+
157+
/**
158+
* Name attribute value for session Cookies.
159+
*/
160+
private String name = COOKIE_NAME;
161+
162+
/**
163+
* Domain attribute value for session Cookies.
164+
*/
165+
private String domain;
166+
167+
/**
168+
* Path attribute value for session Cookies.
169+
*/
170+
private String path;
171+
172+
/**
173+
* Maximum age of the session cookie. If a duration suffix is not specified,
174+
* seconds will be used. A positive value indicates when the cookie expires
175+
* relative to the current time. A value of 0 means the cookie should expire
176+
* immediately. A negative value means no "Max-Age" attribute in which case the
177+
* cookie is removed when the browser is closed.
178+
*/
179+
@DurationUnit(ChronoUnit.SECONDS)
180+
private Duration maxAge = Duration.ofSeconds(-1);
181+
182+
/**
183+
* HttpOnly attribute value for session Cookies.
184+
*/
185+
private Boolean httpOnly = true;
186+
187+
/**
188+
* Secure attribute value for session Cookies.
189+
*/
190+
private Boolean secure;
191+
137192
/**
138193
* SameSite attribute value for session Cookies.
139194
*/
140195
private SameSite sameSite = SameSite.LAX;
141196

197+
/**
198+
* Return the session cookie name.
199+
* @return the session cookie name
200+
*/
201+
public String getName() {
202+
return this.name;
203+
}
204+
205+
public void setName(String name) {
206+
this.name = name;
207+
}
208+
209+
/**
210+
* Return the domain for the session cookie.
211+
* @return the session cookie domain
212+
*/
213+
public String getDomain() {
214+
return this.domain;
215+
}
216+
217+
public void setDomain(String domain) {
218+
this.domain = domain;
219+
}
220+
221+
/**
222+
* Return the path of the session cookie.
223+
* @return the session cookie path
224+
*/
225+
public String getPath() {
226+
return this.path;
227+
}
228+
229+
public void setPath(String path) {
230+
this.path = path;
231+
}
232+
233+
/**
234+
* Return the maximum age of the session cookie.
235+
* @return the maximum age of the session cookie
236+
*/
237+
public Duration getMaxAge() {
238+
return this.maxAge;
239+
}
240+
241+
public void setMaxAge(Duration maxAge) {
242+
this.maxAge = maxAge;
243+
}
244+
245+
/**
246+
* Return whether to use "HttpOnly" cookies for session cookies.
247+
* @return {@code true} to use "HttpOnly" cookies for session cookies.
248+
*/
249+
public Boolean getHttpOnly() {
250+
return this.httpOnly;
251+
}
252+
253+
public void setHttpOnly(Boolean httpOnly) {
254+
this.httpOnly = httpOnly;
255+
}
256+
257+
/**
258+
* Return whether to always mark the session cookie as secure.
259+
* @return {@code true} to mark the session cookie as secure even if the request
260+
* that initiated the corresponding session is using plain HTTP
261+
*/
262+
public Boolean getSecure() {
263+
return this.secure;
264+
}
265+
266+
public void setSecure(Boolean secure) {
267+
this.secure = secure;
268+
}
269+
142270
public SameSite getSameSite() {
143271
return this.sameSite;
144272
}

0 commit comments

Comments
 (0)