Merge pull request #15163 from Vedran Pavic
* gh-15163: Polish "Auto-configure Spring Session's cookie serializer" Auto-configure Spring Session's cookie serializer
This commit is contained in:
		
						commit
						ab8e4d5987
					
				| 
						 | 
				
			
			@ -28,6 +28,8 @@ import org.springframework.boot.WebApplicationType;
 | 
			
		|||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
 | 
			
		||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
 | 
			
		||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
 | 
			
		||||
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.ConditionalOnWebApplication;
 | 
			
		||||
| 
						 | 
				
			
			@ -39,9 +41,14 @@ import org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfig
 | 
			
		|||
import org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration;
 | 
			
		||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
 | 
			
		||||
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
 | 
			
		||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
 | 
			
		||||
import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration;
 | 
			
		||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
 | 
			
		||||
import org.springframework.boot.context.properties.PropertyMapper;
 | 
			
		||||
import org.springframework.boot.web.servlet.server.Session.Cookie;
 | 
			
		||||
import org.springframework.context.ApplicationContext;
 | 
			
		||||
import org.springframework.context.annotation.Bean;
 | 
			
		||||
import org.springframework.context.annotation.Conditional;
 | 
			
		||||
import org.springframework.context.annotation.Configuration;
 | 
			
		||||
import org.springframework.context.annotation.Import;
 | 
			
		||||
import org.springframework.context.annotation.ImportSelector;
 | 
			
		||||
| 
						 | 
				
			
			@ -49,6 +56,10 @@ import org.springframework.core.type.AnnotationMetadata;
 | 
			
		|||
import org.springframework.session.ReactiveSessionRepository;
 | 
			
		||||
import org.springframework.session.Session;
 | 
			
		||||
import org.springframework.session.SessionRepository;
 | 
			
		||||
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.StringUtils;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			@ -64,7 +75,7 @@ import org.springframework.util.StringUtils;
 | 
			
		|||
@Configuration
 | 
			
		||||
@ConditionalOnClass(Session.class)
 | 
			
		||||
@ConditionalOnWebApplication
 | 
			
		||||
@EnableConfigurationProperties(SessionProperties.class)
 | 
			
		||||
@EnableConfigurationProperties({ ServerProperties.class, SessionProperties.class })
 | 
			
		||||
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HazelcastAutoConfiguration.class,
 | 
			
		||||
		JdbcTemplateAutoConfiguration.class, MongoDataAutoConfiguration.class,
 | 
			
		||||
		MongoReactiveDataAutoConfiguration.class, RedisAutoConfiguration.class,
 | 
			
		||||
| 
						 | 
				
			
			@ -78,6 +89,23 @@ public class SessionAutoConfiguration {
 | 
			
		|||
			SessionRepositoryFilterConfiguration.class })
 | 
			
		||||
	static class ServletSessionConfiguration {
 | 
			
		||||
 | 
			
		||||
		@Bean
 | 
			
		||||
		@Conditional(DefaultCookieSerializerCondition.class)
 | 
			
		||||
		public DefaultCookieSerializer cookieSerializer(
 | 
			
		||||
				ServerProperties serverProperties) {
 | 
			
		||||
			Cookie cookie = serverProperties.getServlet().getSession().getCookie();
 | 
			
		||||
			DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
 | 
			
		||||
			PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
 | 
			
		||||
			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).to((maxAge) -> cookieSerializer
 | 
			
		||||
					.setCookieMaxAge((int) maxAge.getSeconds()));
 | 
			
		||||
			return cookieSerializer;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		@Configuration
 | 
			
		||||
		@ConditionalOnMissingBean(SessionRepository.class)
 | 
			
		||||
		@Import({ ServletSessionRepositoryImplementationValidator.class,
 | 
			
		||||
| 
						 | 
				
			
			@ -103,6 +131,31 @@ public class SessionAutoConfiguration {
 | 
			
		|||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Condition to trigger the creation of a {@link DefaultCookieSerializer}. This kicks
 | 
			
		||||
	 * in if either no {@link HttpSessionIdResolver} and {@link CookieSerializer} beans
 | 
			
		||||
	 * are registered, or if {@link CookieHttpSessionIdResolver} is registered but
 | 
			
		||||
	 * {@link CookieSerializer} is not.
 | 
			
		||||
	 */
 | 
			
		||||
	static class DefaultCookieSerializerCondition extends AnyNestedCondition {
 | 
			
		||||
 | 
			
		||||
		DefaultCookieSerializerCondition() {
 | 
			
		||||
			super(ConfigurationPhase.REGISTER_BEAN);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		@ConditionalOnMissingBean({ HttpSessionIdResolver.class, CookieSerializer.class })
 | 
			
		||||
		static class NoComponentsAvailable {
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		@ConditionalOnBean(CookieHttpSessionIdResolver.class)
 | 
			
		||||
		@ConditionalOnMissingBean(CookieSerializer.class)
 | 
			
		||||
		static class CookieHttpSessionIdResolverAvailable {
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * {@link ImportSelector} base class to add {@link StoreType} configuration classes.
 | 
			
		||||
	 */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,11 +26,9 @@ import org.junit.Test;
 | 
			
		|||
 | 
			
		||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
 | 
			
		||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
 | 
			
		||||
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
 | 
			
		||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
 | 
			
		||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
 | 
			
		||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
 | 
			
		||||
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
 | 
			
		||||
import org.springframework.context.annotation.Bean;
 | 
			
		||||
import org.springframework.context.annotation.Configuration;
 | 
			
		||||
import org.springframework.session.MapSessionRepository;
 | 
			
		||||
| 
						 | 
				
			
			@ -38,10 +36,13 @@ import org.springframework.session.SessionRepository;
 | 
			
		|||
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
 | 
			
		||||
import org.springframework.session.web.http.CookieHttpSessionIdResolver;
 | 
			
		||||
import org.springframework.session.web.http.DefaultCookieSerializer;
 | 
			
		||||
import org.springframework.session.web.http.HeaderHttpSessionIdResolver;
 | 
			
		||||
import org.springframework.session.web.http.HttpSessionIdResolver;
 | 
			
		||||
import org.springframework.session.web.http.SessionRepositoryFilter;
 | 
			
		||||
import org.springframework.test.util.ReflectionTestUtils;
 | 
			
		||||
 | 
			
		||||
import static org.assertj.core.api.Assertions.assertThat;
 | 
			
		||||
import static org.mockito.Mockito.mock;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests for {@link SessionAutoConfiguration}.
 | 
			
		||||
| 
						 | 
				
			
			@ -165,27 +166,85 @@ public class SessionAutoConfigurationTests extends AbstractSessionAutoConfigurat
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void sessionCookieConfigurationIsPickedUp() {
 | 
			
		||||
		WebApplicationContextRunner webRunner = new WebApplicationContextRunner(
 | 
			
		||||
				AnnotationConfigServletWebServerApplicationContext::new)
 | 
			
		||||
						.withConfiguration(AutoConfigurations
 | 
			
		||||
								.of(ServletWebServerFactoryAutoConfiguration.class))
 | 
			
		||||
						.withUserConfiguration(SessionRepositoryConfiguration.class)
 | 
			
		||||
						.withPropertyValues("server.port=0",
 | 
			
		||||
								"server.servlet.session.cookie.name=testname");
 | 
			
		||||
		webRunner.run((context) -> {
 | 
			
		||||
	public void sessionCookieConfigurationIsAppliedToAutoConfiguredCookieSerializer() {
 | 
			
		||||
		this.contextRunner.withUserConfiguration(SessionRepositoryConfiguration.class)
 | 
			
		||||
				.withPropertyValues("server.servlet.session.cookie.name=sid",
 | 
			
		||||
						"server.servlet.session.cookie.domain=spring",
 | 
			
		||||
						"server.servlet.session.cookie.path=/test",
 | 
			
		||||
						"server.servlet.session.cookie.httpOnly=false",
 | 
			
		||||
						"server.servlet.session.cookie.secure=false",
 | 
			
		||||
						"server.servlet.session.cookie.maxAge=10s")
 | 
			
		||||
				.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);
 | 
			
		||||
				});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void autoConfiguredCookieSerializerIsUsedBySessionRepositoryFilter() {
 | 
			
		||||
		this.contextRunner.withUserConfiguration(SessionRepositoryConfiguration.class)
 | 
			
		||||
				.withPropertyValues("server.port=0").run((context) -> {
 | 
			
		||||
					SessionRepositoryFilter<?> filter = context
 | 
			
		||||
							.getBean(SessionRepositoryFilter.class);
 | 
			
		||||
					CookieHttpSessionIdResolver sessionIdResolver = (CookieHttpSessionIdResolver) ReflectionTestUtils
 | 
			
		||||
							.getField(filter, "httpSessionIdResolver");
 | 
			
		||||
					DefaultCookieSerializer cookieSerializer = (DefaultCookieSerializer) ReflectionTestUtils
 | 
			
		||||
							.getField(sessionIdResolver, "cookieSerializer");
 | 
			
		||||
			String cookieName = (String) ReflectionTestUtils.getField(cookieSerializer,
 | 
			
		||||
					"cookieName");
 | 
			
		||||
			assertThat(cookieName).isEqualTo("testname");
 | 
			
		||||
					assertThat(cookieSerializer)
 | 
			
		||||
							.isSameAs(context.getBean(DefaultCookieSerializer.class));
 | 
			
		||||
				});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void autoConfiguredCookieSerializerBacksOffWhenUserConfiguresACookieSerializer() {
 | 
			
		||||
		this.contextRunner
 | 
			
		||||
				.withUserConfiguration(UserProvidedCookieSerializerConfiguration.class)
 | 
			
		||||
				.run((context) -> {
 | 
			
		||||
					assertThat(context).hasSingleBean(DefaultCookieSerializer.class);
 | 
			
		||||
					assertThat(context).hasBean("myCookieSerializer");
 | 
			
		||||
				});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void cookiesSerializerIsAutoConfiguredWhenUserConfiguresCookieHttpSessionIdResolver() {
 | 
			
		||||
		this.contextRunner
 | 
			
		||||
				.withUserConfiguration(
 | 
			
		||||
						UserProvidedCookieHttpSessionStrategyConfiguration.class)
 | 
			
		||||
				.run((context) -> assertThat(
 | 
			
		||||
						context.getBeansOfType(DefaultCookieSerializer.class))
 | 
			
		||||
								.isNotEmpty());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void autoConfiguredCookieSerializerBacksOffWhenUserConfiguresHeaderHttpSessionIdResolver() {
 | 
			
		||||
		this.contextRunner
 | 
			
		||||
				.withUserConfiguration(
 | 
			
		||||
						UserProvidedHeaderHttpSessionStrategyConfiguration.class)
 | 
			
		||||
				.run((context) -> assertThat(
 | 
			
		||||
						context.getBeansOfType(DefaultCookieSerializer.class)).isEmpty());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	public void autoConfiguredCookieSerializerBacksOffWhenUserConfiguresCustomHttpSessionIdResolver() {
 | 
			
		||||
		this.contextRunner
 | 
			
		||||
				.withUserConfiguration(
 | 
			
		||||
						UserProvidedCustomHttpSessionStrategyConfiguration.class)
 | 
			
		||||
				.run((context) -> assertThat(
 | 
			
		||||
						context.getBeansOfType(DefaultCookieSerializer.class)).isEmpty());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Configuration
 | 
			
		||||
	@EnableSpringHttpSession
 | 
			
		||||
	static class SessionRepositoryConfiguration {
 | 
			
		||||
| 
						 | 
				
			
			@ -202,4 +261,52 @@ public class SessionAutoConfigurationTests extends AbstractSessionAutoConfigurat
 | 
			
		|||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Configuration
 | 
			
		||||
	@EnableSpringHttpSession
 | 
			
		||||
	static class UserProvidedCookieSerializerConfiguration
 | 
			
		||||
			extends SessionRepositoryConfiguration {
 | 
			
		||||
 | 
			
		||||
		@Bean
 | 
			
		||||
		public DefaultCookieSerializer myCookieSerializer() {
 | 
			
		||||
			return new DefaultCookieSerializer();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Configuration
 | 
			
		||||
	@EnableSpringHttpSession
 | 
			
		||||
	static class UserProvidedCookieHttpSessionStrategyConfiguration
 | 
			
		||||
			extends SessionRepositoryConfiguration {
 | 
			
		||||
 | 
			
		||||
		@Bean
 | 
			
		||||
		public CookieHttpSessionIdResolver httpSessionStrategy() {
 | 
			
		||||
			return new CookieHttpSessionIdResolver();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Configuration
 | 
			
		||||
	@EnableSpringHttpSession
 | 
			
		||||
	static class UserProvidedHeaderHttpSessionStrategyConfiguration
 | 
			
		||||
			extends SessionRepositoryConfiguration {
 | 
			
		||||
 | 
			
		||||
		@Bean
 | 
			
		||||
		public HeaderHttpSessionIdResolver httpSessionStrategy() {
 | 
			
		||||
			return HeaderHttpSessionIdResolver.xAuthToken();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Configuration
 | 
			
		||||
	@EnableSpringHttpSession
 | 
			
		||||
	static class UserProvidedCustomHttpSessionStrategyConfiguration
 | 
			
		||||
			extends SessionRepositoryConfiguration {
 | 
			
		||||
 | 
			
		||||
		@Bean
 | 
			
		||||
		public HttpSessionIdResolver httpSessionStrategy() {
 | 
			
		||||
			return mock(HttpSessionIdResolver.class);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue