Add SameSite session cookie config property for WebFlux
This commit adds a new `spring.webflux.session.cookie.same-site` confuguration property that sets the default value for the "SameSite" attribute in the WebFlux session cookies. Closes gh-20970
This commit is contained in:
parent
5b111093c6
commit
dc6b5badb8
|
@ -75,6 +75,9 @@ import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
|
||||||
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
|
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
|
||||||
import org.springframework.web.server.i18n.FixedLocaleContextResolver;
|
import org.springframework.web.server.i18n.FixedLocaleContextResolver;
|
||||||
import org.springframework.web.server.i18n.LocaleContextResolver;
|
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}.
|
* {@link EnableAutoConfiguration Auto-configuration} for {@link EnableWebFlux WebFlux}.
|
||||||
|
@ -302,6 +305,17 @@ public class WebFluxAutoConfiguration {
|
||||||
return localeContextResolver;
|
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)
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
|
|
@ -35,6 +35,8 @@ public class WebFluxProperties {
|
||||||
|
|
||||||
private final Format format = new Format();
|
private final Format format = new Format();
|
||||||
|
|
||||||
|
private final Session session = new Session();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Path pattern used for static resources.
|
* Path pattern used for static resources.
|
||||||
*/
|
*/
|
||||||
|
@ -65,6 +67,10 @@ public class WebFluxProperties {
|
||||||
return this.format;
|
return this.format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Session getSession() {
|
||||||
|
return this.session;
|
||||||
|
}
|
||||||
|
|
||||||
public String getStaticPathPattern() {
|
public String getStaticPathPattern() {
|
||||||
return this.staticPathPattern;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1766,6 +1766,10 @@
|
||||||
"description": "Whether to enable Spring's HiddenHttpMethodFilter.",
|
"description": "Whether to enable Spring's HiddenHttpMethodFilter.",
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "spring.webflux.session.cookie.same-site",
|
||||||
|
"defaultValue": "lax"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "spring.webservices.wsdl-locations",
|
"name": "spring.webservices.wsdl-locations",
|
||||||
"type": "java.util.List<java.lang.String>",
|
"type": "java.util.List<java.lang.String>",
|
||||||
|
|
|
@ -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.ViewResolutionResultHandler;
|
||||||
import org.springframework.web.reactive.result.view.ViewResolver;
|
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
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.AcceptHeaderLocaleContextResolver;
|
||||||
import org.springframework.web.server.i18n.FixedLocaleContextResolver;
|
import org.springframework.web.server.i18n.FixedLocaleContextResolver;
|
||||||
import org.springframework.web.server.i18n.LocaleContextResolver;
|
import org.springframework.web.server.i18n.LocaleContextResolver;
|
||||||
|
import org.springframework.web.server.session.WebSessionManager;
|
||||||
import org.springframework.web.util.pattern.PathPattern;
|
import org.springframework.web.util.pattern.PathPattern;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
@ -122,6 +125,8 @@ class WebFluxAutoConfigurationTests {
|
||||||
assertThat(context).getBeans(RequestMappingHandlerAdapter.class).hasSize(1);
|
assertThat(context).getBeans(RequestMappingHandlerAdapter.class).hasSize(1);
|
||||||
assertThat(context).getBeans(RequestedContentTypeResolver.class).hasSize(1);
|
assertThat(context).getBeans(RequestedContentTypeResolver.class).hasSize(1);
|
||||||
assertThat(context).getBeans(RouterFunctionMapping.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();
|
assertThat(context.getBean("resourceHandlerMapping", HandlerMapping.class)).isNotNull();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -557,6 +562,20 @@ class WebFluxAutoConfigurationTests {
|
||||||
HighPrecedenceConfigurer.class, WebFluxConfig.class, LowPrecedenceConfigurer.class));
|
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<PathPattern, Object> getHandlerMap(ApplicationContext context) {
|
private Map<PathPattern, Object> getHandlerMap(ApplicationContext context) {
|
||||||
HandlerMapping mapping = context.getBean("resourceHandlerMapping", HandlerMapping.class);
|
HandlerMapping mapping = context.getBean("resourceHandlerMapping", HandlerMapping.class);
|
||||||
if (mapping instanceof SimpleUrlHandlerMapping) {
|
if (mapping instanceof SimpleUrlHandlerMapping) {
|
||||||
|
|
Loading…
Reference in New Issue