Merge pull request #42316 from nosan

* pr/42316:
  Polish "Add support for partitioned cookies"
  Add support for partitioned cookies

Closes gh-42316
This commit is contained in:
Moritz Halbritter 2024-09-25 16:15:54 +02:00
commit eb7b6a776d
8 changed files with 40 additions and 5 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -98,6 +98,7 @@ public class SessionAutoConfiguration {
map.from(cookie::getSecure).to(cookieSerializer::setUseSecureCookie);
map.from(cookie::getMaxAge).asInt(Duration::getSeconds).to(cookieSerializer::setCookieMaxAge);
map.from(cookie::getSameSite).as(SameSite::attributeValue).to(cookieSerializer::setSameSite);
map.from(cookie::getPartitioned).to(cookieSerializer::setPartitioned);
cookieSerializerCustomizers.orderedStream().forEach((customizer) -> customizer.customize(cookieSerializer));
return cookieSerializer;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -76,6 +76,7 @@ public class WebSessionIdResolverAutoConfiguration {
map.from(cookie::getHttpOnly).to(builder::httpOnly);
map.from(cookie::getSecure).to(builder::secure);
map.from(cookie::getMaxAge).to(builder::maxAge);
map.from(cookie::getPartitioned).to(builder::partitioned);
map.from(getSameSite(cookie)).to(builder::sameSite);
}

View File

@ -143,6 +143,10 @@
"name": "server.reactive.session.cookie.name",
"description": "Name for the cookie."
},
{
"name": "server.reactive.session.cookie.partitioned",
"description": "Whether the generated cookie carries the Partitioned attribute."
},
{
"name": "server.reactive.session.cookie.path",
"description": "Path of the cookie."
@ -229,6 +233,10 @@
"name": "server.servlet.session.cookie.name",
"description": "Name of the cookie."
},
{
"name": "server.servlet.session.cookie.partitioned",
"description": "Whether the generated cookie carries the Partitioned attribute."
},
{
"name": "server.servlet.session.cookie.path",
"description": "Path of the cookie."

View File

@ -156,7 +156,7 @@ class SessionAutoConfigurationTests extends AbstractSessionAutoConfigurationTest
.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",
"server.servlet.session.cookie.sameSite=strict")
"server.servlet.session.cookie.sameSite=strict", "server.servlet.session.cookie.partitioned=true")
.run((context) -> {
DefaultCookieSerializer cookieSerializer = context.getBean(DefaultCookieSerializer.class);
assertThat(cookieSerializer).hasFieldOrPropertyWithValue("cookieName", "sid");
@ -166,6 +166,7 @@ class SessionAutoConfigurationTests extends AbstractSessionAutoConfigurationTest
assertThat(cookieSerializer).hasFieldOrPropertyWithValue("useSecureCookie", false);
assertThat(cookieSerializer).hasFieldOrPropertyWithValue("cookieMaxAge", 10);
assertThat(cookieSerializer).hasFieldOrPropertyWithValue("sameSite", "Strict");
assertThat(cookieSerializer).hasFieldOrPropertyWithValue("partitioned", true);
});
}

View File

@ -644,7 +644,8 @@ class WebFluxAutoConfigurationTests {
this.contextRunner.withPropertyValues("server.reactive.session.cookie.name:JSESSIONID",
"server.reactive.session.cookie.domain:.example.com", "server.reactive.session.cookie.path:/example",
"server.reactive.session.cookie.max-age:60", "server.reactive.session.cookie.http-only:false",
"server.reactive.session.cookie.secure:false", "server.reactive.session.cookie.same-site:strict")
"server.reactive.session.cookie.secure:false", "server.reactive.session.cookie.same-site:strict",
"server.reactive.session.cookie.partitioned:true")
.run(assertExchangeWithSession((exchange) -> {
List<ResponseCookie> cookies = exchange.getResponse().getCookies().get("JSESSIONID");
assertThat(cookies).isNotEmpty();
@ -654,6 +655,7 @@ class WebFluxAutoConfigurationTests {
assertThat(cookies).allMatch((cookie) -> !cookie.isHttpOnly());
assertThat(cookies).allMatch((cookie) -> !cookie.isSecure());
assertThat(cookies).allMatch((cookie) -> cookie.getSameSite().equals("Strict"));
assertThat(cookies).allMatch(ResponseCookie::isPartitioned);
}));
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -57,6 +57,11 @@ public class Cookie {
*/
private Boolean secure;
/**
* Whether the generated cookie carries the Partitioned attribute.
*/
private Boolean partitioned;
/**
* Maximum age of the cookie. If a duration suffix is not specified, seconds will be
* used. A positive value indicates when the cookie expires relative to the current
@ -127,6 +132,14 @@ public class Cookie {
this.sameSite = sameSite;
}
public Boolean getPartitioned() {
return this.partitioned;
}
public void setPartitioned(Boolean partitioned) {
this.partitioned = partitioned;
}
/**
* SameSite values.
*/

View File

@ -60,6 +60,8 @@ import org.springframework.util.ClassUtils;
public abstract class AbstractServletWebServerFactory extends AbstractConfigurableWebServerFactory
implements ConfigurableServletWebServerFactory {
private static final String PARTITIONED_ATTRIBUTE_NAME = "Partitioned";
protected final Log logger = LogFactory.getLog(getClass());
private String contextPath = "";
@ -350,6 +352,9 @@ public abstract class AbstractServletWebServerFactory extends AbstractConfigurab
map.from(cookie::getHttpOnly).to(config::setHttpOnly);
map.from(cookie::getSecure).to(config::setSecure);
map.from(cookie::getMaxAge).asInt(Duration::getSeconds).to(config::setMaxAge);
map.from(cookie::getPartitioned)
.as(Object::toString)
.to((partitioned) -> config.setAttribute(PARTITIONED_ATTRIBUTE_NAME, partitioned));
}
private Set<jakarta.servlet.SessionTrackingMode> unwrap(Set<Session.SessionTrackingMode> modes) {

View File

@ -863,6 +863,7 @@ public abstract class AbstractServletWebServerFactoryTests {
factory.getSession().getCookie().setPath("/testpath");
factory.getSession().getCookie().setHttpOnly(true);
factory.getSession().getCookie().setSecure(true);
factory.getSession().getCookie().setPartitioned(true);
factory.getSession().getCookie().setMaxAge(Duration.ofSeconds(60));
final AtomicReference<SessionCookieConfig> configReference = new AtomicReference<>();
this.webServer = factory.getWebServer((context) -> configReference.set(context.getSessionCookieConfig()));
@ -872,6 +873,7 @@ public abstract class AbstractServletWebServerFactoryTests {
assertThat(sessionCookieConfig.getPath()).isEqualTo("/testpath");
assertThat(sessionCookieConfig.isHttpOnly()).isTrue();
assertThat(sessionCookieConfig.isSecure()).isTrue();
assertThat(sessionCookieConfig.getAttribute("Partitioned")).isEqualTo("true");
assertThat(sessionCookieConfig.getMaxAge()).isEqualTo(60);
}
@ -1166,6 +1168,7 @@ public abstract class AbstractServletWebServerFactoryTests {
factory.getSession().getCookie().setPath("/testpath");
factory.getSession().getCookie().setHttpOnly(true);
factory.getSession().getCookie().setSecure(true);
factory.getSession().getCookie().setPartitioned(false);
factory.getSession().getCookie().setMaxAge(Duration.ofMinutes(1));
AtomicReference<ServletContext> contextReference = new AtomicReference<>();
factory.getWebServer(contextReference::set).start();
@ -1178,6 +1181,7 @@ public abstract class AbstractServletWebServerFactoryTests {
assertThat(servletContext.getSessionCookieConfig().isHttpOnly()).isTrue();
assertThat(servletContext.getSessionCookieConfig().isSecure()).isTrue();
assertThat(servletContext.getSessionCookieConfig().getMaxAge()).isEqualTo(60);
assertThat(servletContext.getSessionCookieConfig().getAttribute("Partitioned")).isEqualTo("false");
}
@Test