Skip to content

Commit 0daf83e

Browse files
vpavicwilkinsona
authored andcommitted
Auto-configure Spring Session's cookie serializer
See gh-15163
1 parent 516d0ef commit 0daf83e

File tree

2 files changed

+178
-1
lines changed

2 files changed

+178
-1
lines changed

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

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
2929
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
3030
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
31+
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
32+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
3133
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3234
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3335
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
@@ -39,16 +41,25 @@
3941
import org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration;
4042
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
4143
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
44+
import org.springframework.boot.autoconfigure.web.ServerProperties;
4245
import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration;
4346
import org.springframework.boot.context.properties.EnableConfigurationProperties;
47+
import org.springframework.boot.context.properties.PropertyMapper;
48+
import org.springframework.boot.web.servlet.server.Session.Cookie;
4449
import org.springframework.context.ApplicationContext;
50+
import org.springframework.context.annotation.Bean;
51+
import org.springframework.context.annotation.Conditional;
4552
import org.springframework.context.annotation.Configuration;
4653
import org.springframework.context.annotation.Import;
4754
import org.springframework.context.annotation.ImportSelector;
4855
import org.springframework.core.type.AnnotationMetadata;
4956
import org.springframework.session.ReactiveSessionRepository;
5057
import org.springframework.session.Session;
5158
import org.springframework.session.SessionRepository;
59+
import org.springframework.session.web.http.CookieHttpSessionIdResolver;
60+
import org.springframework.session.web.http.CookieSerializer;
61+
import org.springframework.session.web.http.DefaultCookieSerializer;
62+
import org.springframework.session.web.http.HttpSessionIdResolver;
5263
import org.springframework.util.StringUtils;
5364

5465
/**
@@ -64,7 +75,7 @@
6475
@Configuration
6576
@ConditionalOnClass(Session.class)
6677
@ConditionalOnWebApplication
67-
@EnableConfigurationProperties(SessionProperties.class)
78+
@EnableConfigurationProperties({ ServerProperties.class, SessionProperties.class })
6879
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HazelcastAutoConfiguration.class,
6980
JdbcTemplateAutoConfiguration.class, MongoDataAutoConfiguration.class,
7081
MongoReactiveDataAutoConfiguration.class, RedisAutoConfiguration.class,
@@ -78,6 +89,28 @@ public class SessionAutoConfiguration {
7889
SessionRepositoryFilterConfiguration.class })
7990
static class ServletSessionConfiguration {
8091

92+
private final ServerProperties serverProperties;
93+
94+
ServletSessionConfiguration(ServerProperties serverProperties) {
95+
this.serverProperties = serverProperties;
96+
}
97+
98+
@Bean
99+
@Conditional(DefaultCookieSerializerCondition.class)
100+
public DefaultCookieSerializer cookieSerializer() {
101+
Cookie cookie = this.serverProperties.getServlet().getSession().getCookie();
102+
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
103+
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
104+
map.from(cookie::getName).to(cookieSerializer::setCookieName);
105+
map.from(cookie::getDomain).to(cookieSerializer::setDomainName);
106+
map.from(cookie::getPath).to(cookieSerializer::setCookiePath);
107+
map.from(cookie::getHttpOnly).to(cookieSerializer::setUseHttpOnlyCookie);
108+
map.from(cookie::getSecure).to(cookieSerializer::setUseSecureCookie);
109+
map.from(cookie::getMaxAge).to((maxAge) -> cookieSerializer
110+
.setCookieMaxAge((int) maxAge.getSeconds()));
111+
return cookieSerializer;
112+
}
113+
81114
@Configuration
82115
@ConditionalOnMissingBean(SessionRepository.class)
83116
@Import({ ServletSessionRepositoryImplementationValidator.class,
@@ -103,6 +136,31 @@ static class ReactiveSessionRepositoryConfiguration {
103136

104137
}
105138

139+
/**
140+
* Condition to trigger the creation of a {@link DefaultCookieSerializer}. This kicks
141+
* in if either no {@link HttpSessionIdResolver} and {@link CookieSerializer} beans
142+
* are registered, or if {@link CookieHttpSessionIdResolver} is registered but
143+
* {@link CookieSerializer} is not.
144+
*/
145+
static class DefaultCookieSerializerCondition extends AnyNestedCondition {
146+
147+
DefaultCookieSerializerCondition() {
148+
super(ConfigurationPhase.REGISTER_BEAN);
149+
}
150+
151+
@ConditionalOnMissingBean({ HttpSessionIdResolver.class, CookieSerializer.class })
152+
static class NoComponentsAvailable {
153+
154+
}
155+
156+
@ConditionalOnBean(CookieHttpSessionIdResolver.class)
157+
@ConditionalOnMissingBean(CookieSerializer.class)
158+
static class CookieHttpSessionIdResolverAvailable {
159+
160+
}
161+
162+
}
163+
106164
/**
107165
* {@link ImportSelector} base class to add {@link StoreType} configuration classes.
108166
*/

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationTests.java

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,13 @@
3838
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
3939
import org.springframework.session.web.http.CookieHttpSessionIdResolver;
4040
import org.springframework.session.web.http.DefaultCookieSerializer;
41+
import org.springframework.session.web.http.HeaderHttpSessionIdResolver;
42+
import org.springframework.session.web.http.HttpSessionIdResolver;
4143
import org.springframework.session.web.http.SessionRepositoryFilter;
4244
import org.springframework.test.util.ReflectionTestUtils;
4345

4446
import static org.assertj.core.api.Assertions.assertThat;
47+
import static org.mockito.Mockito.mock;
4548

4649
/**
4750
* Tests for {@link SessionAutoConfiguration}.
@@ -186,6 +189,74 @@ public void sessionCookieConfigurationIsPickedUp() {
186189
});
187190
}
188191

192+
@Test
193+
public void autoConfiguredCookieSerializerConfiguration() {
194+
this.contextRunner.withUserConfiguration(SessionRepositoryConfiguration.class)
195+
.withPropertyValues("server.servlet.session.cookie.name=sid",
196+
"server.servlet.session.cookie.domain=spring",
197+
"server.servlet.session.cookie.path=/test",
198+
"server.servlet.session.cookie.httpOnly=false",
199+
"server.servlet.session.cookie.secure=false",
200+
"server.servlet.session.cookie.maxAge=10s")
201+
.run((context) -> {
202+
DefaultCookieSerializer cookieSerializer = context
203+
.getBean(DefaultCookieSerializer.class);
204+
assertThat(cookieSerializer).hasFieldOrPropertyWithValue("cookieName",
205+
"sid");
206+
assertThat(cookieSerializer).hasFieldOrPropertyWithValue("domainName",
207+
"spring");
208+
assertThat(cookieSerializer).hasFieldOrPropertyWithValue("cookiePath",
209+
"/test");
210+
assertThat(cookieSerializer)
211+
.hasFieldOrPropertyWithValue("useHttpOnlyCookie", false);
212+
assertThat(cookieSerializer)
213+
.hasFieldOrPropertyWithValue("useSecureCookie", false);
214+
assertThat(cookieSerializer)
215+
.hasFieldOrPropertyWithValue("cookieMaxAge", 10);
216+
});
217+
}
218+
219+
@Test
220+
public void userProvidedCookieSerializerConfiguration() {
221+
this.contextRunner
222+
.withUserConfiguration(UserProvidedCookieSerializerConfiguration.class)
223+
.withPropertyValues("server.servlet.session.cookie.name=sid")
224+
.run((context) -> {
225+
DefaultCookieSerializer cookieSerializer = context
226+
.getBean(DefaultCookieSerializer.class);
227+
assertThat(cookieSerializer).hasFieldOrPropertyWithValue("cookieName",
228+
"SESSION");
229+
});
230+
}
231+
232+
@Test
233+
public void userProvidedCookieHttpSessionStrategyConfiguration() {
234+
this.contextRunner
235+
.withUserConfiguration(
236+
UserProvidedCookieHttpSessionStrategyConfiguration.class)
237+
.run((context) -> assertThat(
238+
context.getBeansOfType(DefaultCookieSerializer.class))
239+
.isNotEmpty());
240+
}
241+
242+
@Test
243+
public void userProvidedHeaderHttpSessionStrategyConfiguration() {
244+
this.contextRunner
245+
.withUserConfiguration(
246+
UserProvidedHeaderHttpSessionStrategyConfiguration.class)
247+
.run((context) -> assertThat(
248+
context.getBeansOfType(DefaultCookieSerializer.class)).isEmpty());
249+
}
250+
251+
@Test
252+
public void userProvidedCustomHttpSessionStrategyConfiguration() {
253+
this.contextRunner
254+
.withUserConfiguration(
255+
UserProvidedCustomHttpSessionStrategyConfiguration.class)
256+
.run((context) -> assertThat(
257+
context.getBeansOfType(DefaultCookieSerializer.class)).isEmpty());
258+
}
259+
189260
@Configuration
190261
@EnableSpringHttpSession
191262
static class SessionRepositoryConfiguration {
@@ -202,4 +273,52 @@ static class ServerPropertiesConfiguration {
202273

203274
}
204275

276+
@Configuration
277+
@EnableSpringHttpSession
278+
static class UserProvidedCookieSerializerConfiguration
279+
extends SessionRepositoryConfiguration {
280+
281+
@Bean
282+
public DefaultCookieSerializer myCookieSerializer() {
283+
return new DefaultCookieSerializer();
284+
}
285+
286+
}
287+
288+
@Configuration
289+
@EnableSpringHttpSession
290+
static class UserProvidedCookieHttpSessionStrategyConfiguration
291+
extends SessionRepositoryConfiguration {
292+
293+
@Bean
294+
public CookieHttpSessionIdResolver httpSessionStrategy() {
295+
return new CookieHttpSessionIdResolver();
296+
}
297+
298+
}
299+
300+
@Configuration
301+
@EnableSpringHttpSession
302+
static class UserProvidedHeaderHttpSessionStrategyConfiguration
303+
extends SessionRepositoryConfiguration {
304+
305+
@Bean
306+
public HeaderHttpSessionIdResolver httpSessionStrategy() {
307+
return HeaderHttpSessionIdResolver.xAuthToken();
308+
}
309+
310+
}
311+
312+
@Configuration
313+
@EnableSpringHttpSession
314+
static class UserProvidedCustomHttpSessionStrategyConfiguration
315+
extends SessionRepositoryConfiguration {
316+
317+
@Bean
318+
public HttpSessionIdResolver httpSessionStrategy() {
319+
return mock(HttpSessionIdResolver.class);
320+
}
321+
322+
}
323+
205324
}

0 commit comments

Comments
 (0)