Skip to content

Commit ab8e4d5

Browse files
committed
Merge pull request #15163 from Vedran Pavic
* gh-15163: Polish "Auto-configure Spring Session's cookie serializer" Auto-configure Spring Session's cookie serializer
2 parents 516d0ef + 9553d4f commit ab8e4d5

File tree

2 files changed

+182
-22
lines changed

2 files changed

+182
-22
lines changed

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

Lines changed: 54 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,23 @@ public class SessionAutoConfiguration {
7889
SessionRepositoryFilterConfiguration.class })
7990
static class ServletSessionConfiguration {
8091

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

104132
}
105133

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

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

Lines changed: 128 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,23 @@
2626

2727
import org.springframework.boot.autoconfigure.AutoConfigurations;
2828
import org.springframework.boot.autoconfigure.web.ServerProperties;
29-
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
3029
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3130
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
3231
import org.springframework.boot.web.servlet.FilterRegistrationBean;
33-
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
3432
import org.springframework.context.annotation.Bean;
3533
import org.springframework.context.annotation.Configuration;
3634
import org.springframework.session.MapSessionRepository;
3735
import org.springframework.session.SessionRepository;
3836
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
3937
import org.springframework.session.web.http.CookieHttpSessionIdResolver;
4038
import org.springframework.session.web.http.DefaultCookieSerializer;
39+
import org.springframework.session.web.http.HeaderHttpSessionIdResolver;
40+
import org.springframework.session.web.http.HttpSessionIdResolver;
4141
import org.springframework.session.web.http.SessionRepositoryFilter;
4242
import org.springframework.test.util.ReflectionTestUtils;
4343

4444
import static org.assertj.core.api.Assertions.assertThat;
45+
import static org.mockito.Mockito.mock;
4546

4647
/**
4748
* Tests for {@link SessionAutoConfiguration}.
@@ -165,25 +166,83 @@ public void filterDispatcherTypesCanBeCustomized() {
165166
}
166167

167168
@Test
168-
public void sessionCookieConfigurationIsPickedUp() {
169-
WebApplicationContextRunner webRunner = new WebApplicationContextRunner(
170-
AnnotationConfigServletWebServerApplicationContext::new)
171-
.withConfiguration(AutoConfigurations
172-
.of(ServletWebServerFactoryAutoConfiguration.class))
173-
.withUserConfiguration(SessionRepositoryConfiguration.class)
174-
.withPropertyValues("server.port=0",
175-
"server.servlet.session.cookie.name=testname");
176-
webRunner.run((context) -> {
177-
SessionRepositoryFilter<?> filter = context
178-
.getBean(SessionRepositoryFilter.class);
179-
CookieHttpSessionIdResolver sessionIdResolver = (CookieHttpSessionIdResolver) ReflectionTestUtils
180-
.getField(filter, "httpSessionIdResolver");
181-
DefaultCookieSerializer cookieSerializer = (DefaultCookieSerializer) ReflectionTestUtils
182-
.getField(sessionIdResolver, "cookieSerializer");
183-
String cookieName = (String) ReflectionTestUtils.getField(cookieSerializer,
184-
"cookieName");
185-
assertThat(cookieName).isEqualTo("testname");
186-
});
169+
public void sessionCookieConfigurationIsAppliedToAutoConfiguredCookieSerializer() {
170+
this.contextRunner.withUserConfiguration(SessionRepositoryConfiguration.class)
171+
.withPropertyValues("server.servlet.session.cookie.name=sid",
172+
"server.servlet.session.cookie.domain=spring",
173+
"server.servlet.session.cookie.path=/test",
174+
"server.servlet.session.cookie.httpOnly=false",
175+
"server.servlet.session.cookie.secure=false",
176+
"server.servlet.session.cookie.maxAge=10s")
177+
.run((context) -> {
178+
DefaultCookieSerializer cookieSerializer = context
179+
.getBean(DefaultCookieSerializer.class);
180+
assertThat(cookieSerializer).hasFieldOrPropertyWithValue("cookieName",
181+
"sid");
182+
assertThat(cookieSerializer).hasFieldOrPropertyWithValue("domainName",
183+
"spring");
184+
assertThat(cookieSerializer).hasFieldOrPropertyWithValue("cookiePath",
185+
"/test");
186+
assertThat(cookieSerializer)
187+
.hasFieldOrPropertyWithValue("useHttpOnlyCookie", false);
188+
assertThat(cookieSerializer)
189+
.hasFieldOrPropertyWithValue("useSecureCookie", false);
190+
assertThat(cookieSerializer)
191+
.hasFieldOrPropertyWithValue("cookieMaxAge", 10);
192+
});
193+
}
194+
195+
@Test
196+
public void autoConfiguredCookieSerializerIsUsedBySessionRepositoryFilter() {
197+
this.contextRunner.withUserConfiguration(SessionRepositoryConfiguration.class)
198+
.withPropertyValues("server.port=0").run((context) -> {
199+
SessionRepositoryFilter<?> filter = context
200+
.getBean(SessionRepositoryFilter.class);
201+
CookieHttpSessionIdResolver sessionIdResolver = (CookieHttpSessionIdResolver) ReflectionTestUtils
202+
.getField(filter, "httpSessionIdResolver");
203+
DefaultCookieSerializer cookieSerializer = (DefaultCookieSerializer) ReflectionTestUtils
204+
.getField(sessionIdResolver, "cookieSerializer");
205+
assertThat(cookieSerializer)
206+
.isSameAs(context.getBean(DefaultCookieSerializer.class));
207+
});
208+
}
209+
210+
@Test
211+
public void autoConfiguredCookieSerializerBacksOffWhenUserConfiguresACookieSerializer() {
212+
this.contextRunner
213+
.withUserConfiguration(UserProvidedCookieSerializerConfiguration.class)
214+
.run((context) -> {
215+
assertThat(context).hasSingleBean(DefaultCookieSerializer.class);
216+
assertThat(context).hasBean("myCookieSerializer");
217+
});
218+
}
219+
220+
@Test
221+
public void cookiesSerializerIsAutoConfiguredWhenUserConfiguresCookieHttpSessionIdResolver() {
222+
this.contextRunner
223+
.withUserConfiguration(
224+
UserProvidedCookieHttpSessionStrategyConfiguration.class)
225+
.run((context) -> assertThat(
226+
context.getBeansOfType(DefaultCookieSerializer.class))
227+
.isNotEmpty());
228+
}
229+
230+
@Test
231+
public void autoConfiguredCookieSerializerBacksOffWhenUserConfiguresHeaderHttpSessionIdResolver() {
232+
this.contextRunner
233+
.withUserConfiguration(
234+
UserProvidedHeaderHttpSessionStrategyConfiguration.class)
235+
.run((context) -> assertThat(
236+
context.getBeansOfType(DefaultCookieSerializer.class)).isEmpty());
237+
}
238+
239+
@Test
240+
public void autoConfiguredCookieSerializerBacksOffWhenUserConfiguresCustomHttpSessionIdResolver() {
241+
this.contextRunner
242+
.withUserConfiguration(
243+
UserProvidedCustomHttpSessionStrategyConfiguration.class)
244+
.run((context) -> assertThat(
245+
context.getBeansOfType(DefaultCookieSerializer.class)).isEmpty());
187246
}
188247

189248
@Configuration
@@ -202,4 +261,52 @@ static class ServerPropertiesConfiguration {
202261

203262
}
204263

264+
@Configuration
265+
@EnableSpringHttpSession
266+
static class UserProvidedCookieSerializerConfiguration
267+
extends SessionRepositoryConfiguration {
268+
269+
@Bean
270+
public DefaultCookieSerializer myCookieSerializer() {
271+
return new DefaultCookieSerializer();
272+
}
273+
274+
}
275+
276+
@Configuration
277+
@EnableSpringHttpSession
278+
static class UserProvidedCookieHttpSessionStrategyConfiguration
279+
extends SessionRepositoryConfiguration {
280+
281+
@Bean
282+
public CookieHttpSessionIdResolver httpSessionStrategy() {
283+
return new CookieHttpSessionIdResolver();
284+
}
285+
286+
}
287+
288+
@Configuration
289+
@EnableSpringHttpSession
290+
static class UserProvidedHeaderHttpSessionStrategyConfiguration
291+
extends SessionRepositoryConfiguration {
292+
293+
@Bean
294+
public HeaderHttpSessionIdResolver httpSessionStrategy() {
295+
return HeaderHttpSessionIdResolver.xAuthToken();
296+
}
297+
298+
}
299+
300+
@Configuration
301+
@EnableSpringHttpSession
302+
static class UserProvidedCustomHttpSessionStrategyConfiguration
303+
extends SessionRepositoryConfiguration {
304+
305+
@Bean
306+
public HttpSessionIdResolver httpSessionStrategy() {
307+
return mock(HttpSessionIdResolver.class);
308+
}
309+
310+
}
311+
205312
}

0 commit comments

Comments
 (0)