diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java index 501df5670af..f1790a250fa 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java @@ -75,6 +75,9 @@ import org.springframework.web.server.adapter.WebHttpHandlerBuilder; import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver; import org.springframework.web.server.i18n.FixedLocaleContextResolver; import org.springframework.web.server.i18n.LocaleContextResolver; +import org.springframework.web.server.session.CookieWebSessionIdResolver; +import org.springframework.web.server.session.DefaultWebSessionManager; +import org.springframework.web.server.session.WebSessionManager; /** * {@link EnableAutoConfiguration Auto-configuration} for {@link EnableWebFlux WebFlux}. @@ -302,6 +305,17 @@ public class WebFluxAutoConfiguration { return localeContextResolver; } + @Bean + @ConditionalOnMissingBean(name = WebHttpHandlerBuilder.WEB_SESSION_MANAGER_BEAN_NAME) + public WebSessionManager webSessionManager() { + DefaultWebSessionManager webSessionManager = new DefaultWebSessionManager(); + CookieWebSessionIdResolver webSessionIdResolver = new CookieWebSessionIdResolver(); + webSessionIdResolver.addCookieInitializer((cookie) -> cookie + .sameSite(this.webFluxProperties.getSession().getCookie().getSameSite().attribute())); + webSessionManager.setSessionIdResolver(webSessionIdResolver); + return webSessionManager; + } + } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxProperties.java index db57808b5ed..a04fd0aada7 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxProperties.java @@ -35,6 +35,8 @@ public class WebFluxProperties { private final Format format = new Format(); + private final Session session = new Session(); + /** * Path pattern used for static resources. */ @@ -65,6 +67,10 @@ public class WebFluxProperties { return this.format; } + public Session getSession() { + return this.session; + } + public String getStaticPathPattern() { return this.staticPathPattern; } @@ -116,4 +122,62 @@ public class WebFluxProperties { } + public static class Session { + + private final Cookie cookie = new Cookie(); + + public Cookie getCookie() { + return this.cookie; + } + + } + + public static class Cookie { + + /** + * SameSite attribute value for session Cookies. + */ + private SameSite sameSite = SameSite.LAX; + + public SameSite getSameSite() { + return this.sameSite; + } + + public void setSameSite(SameSite sameSite) { + this.sameSite = sameSite; + } + + } + + public enum SameSite { + + /** + * Cookies are sent in both first-party and cross-origin requests. + */ + NONE("None"), + + /** + * Cookies are sent in a first-party context, also when following a link to the + * origin site. + */ + LAX("Lax"), + + /** + * Cookies are only sent in a first-party context (i.e. not when following a link + * to the origin site). + */ + STRICT("Strict"); + + private final String attribute; + + SameSite(String attribute) { + this.attribute = attribute; + } + + public String attribute() { + return this.attribute; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index ad42df86ff0..e6e52f74997 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -1766,6 +1766,10 @@ "description": "Whether to enable Spring's HiddenHttpMethodFilter.", "defaultValue": false }, + { + "name": "spring.webflux.session.cookie.same-site", + "defaultValue": "lax" + }, { "name": "spring.webservices.wsdl-locations", "type": "java.util.List", diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java index b04e9eefd6c..d63e701e7d8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java @@ -82,9 +82,12 @@ import org.springframework.web.reactive.result.method.annotation.RequestMappingH import org.springframework.web.reactive.result.view.ViewResolutionResultHandler; import org.springframework.web.reactive.result.view.ViewResolver; import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebSession; +import org.springframework.web.server.adapter.WebHttpHandlerBuilder; import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver; import org.springframework.web.server.i18n.FixedLocaleContextResolver; import org.springframework.web.server.i18n.LocaleContextResolver; +import org.springframework.web.server.session.WebSessionManager; import org.springframework.web.util.pattern.PathPattern; import static org.assertj.core.api.Assertions.assertThat; @@ -122,6 +125,8 @@ class WebFluxAutoConfigurationTests { assertThat(context).getBeans(RequestMappingHandlerAdapter.class).hasSize(1); assertThat(context).getBeans(RequestedContentTypeResolver.class).hasSize(1); assertThat(context).getBeans(RouterFunctionMapping.class).hasSize(1); + assertThat(context.getBean(WebHttpHandlerBuilder.WEB_SESSION_MANAGER_BEAN_NAME, WebSessionManager.class)) + .isNotNull(); assertThat(context.getBean("resourceHandlerMapping", HandlerMapping.class)).isNotNull(); }); } @@ -557,6 +562,20 @@ class WebFluxAutoConfigurationTests { HighPrecedenceConfigurer.class, WebFluxConfig.class, LowPrecedenceConfigurer.class)); } + @Test + void customSameSteConfigurationShouldBeApplied() { + this.contextRunner.withPropertyValues("spring.webflux.session.cookie.same-site:strict").run((context) -> { + MockServerHttpRequest request = MockServerHttpRequest.get("/").build(); + MockServerWebExchange exchange = MockServerWebExchange.from(request); + WebSessionManager webSessionManager = context.getBean(WebSessionManager.class); + WebSession webSession = webSessionManager.getSession(exchange).block(); + webSession.start(); + exchange.getResponse().setComplete().block(); + assertThat(exchange.getResponse().getCookies().get("SESSION")).isNotEmpty() + .allMatch((cookie) -> cookie.getSameSite().equals("Strict")); + }); + } + private Map getHandlerMap(ApplicationContext context) { HandlerMapping mapping = context.getBean("resourceHandlerMapping", HandlerMapping.class); if (mapping instanceof SimpleUrlHandlerMapping) {