Skip to content

Add support for omitting the SameSite attribute from the session cookie #44714

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,7 @@ private void initializeCookie(ResponseCookieBuilder builder) {
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);
}

private String getSameSite(Cookie properties) {
SameSite sameSite = properties.getSameSite();
return (sameSite != null) ? sameSite.attributeValue() : null;
map.from(cookie::getSameSite).as(SameSite::attributeValue).to(builder::sameSite);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,16 @@ void sessionCookieConfigurationIsAppliedToAutoConfiguredCookieSerializer() {
});
}

@Test
void sessionCookieSameSiteOmittedIsAppliedToAutoConfiguredCookieSerializer() {
this.contextRunner.withUserConfiguration(SessionRepositoryConfiguration.class)
.withPropertyValues("server.servlet.session.cookie.sameSite=omitted")
.run((context) -> {
DefaultCookieSerializer cookieSerializer = context.getBean(DefaultCookieSerializer.class);
assertThat(cookieSerializer).hasFieldOrPropertyWithValue("sameSite", null);
});
}

@Test
void autoConfiguredCookieSerializerIsUsedBySessionRepositoryFilter() {
this.contextRunner.withUserConfiguration(SessionRepositoryConfiguration.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,15 @@ void customSessionCookieConfigurationShouldBeApplied() {
}));
}

@Test
void sessionCookieOmittedConfigurationShouldBeApplied() {
this.contextRunner.withPropertyValues("server.reactive.session.cookie.same-site:omitted")
.run(assertExchangeWithSession((exchange) -> {
List<ResponseCookie> cookies = exchange.getResponse().getCookies().get("SESSION");
assertThat(cookies).extracting(ResponseCookie::getSameSite).containsOnlyNulls();
}));
}

@ParameterizedTest
@ValueSource(classes = { ServerProperties.class, WebFluxProperties.class })
void propertiesAreNotEnabledInNonWebApplication(Class<?> propertiesClass) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ protected final void configureWebAppContext(WebAppContext context, ServletContex
private void configureSession(WebAppContext context) {
SessionHandler handler = context.getSessionHandler();
SameSite sessionSameSite = getSession().getCookie().getSameSite();
if (sessionSameSite != null) {
if (sessionSameSite != null && sessionSameSite != SameSite.OMITTED) {
handler.setSameSite(HttpCookie.SameSite.valueOf(sessionSameSite.name()));
}
Duration sessionTimeout = getSession().getTimeout();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -998,11 +998,12 @@ private static class SuppliedSameSiteCookieProcessor extends Rfc6265CookieProces
@Override
public String generateHeader(Cookie cookie, HttpServletRequest request) {
SameSite sameSite = getSameSite(cookie);
if (sameSite == null) {
String sameSiteValue = (sameSite != null) ? sameSite.attributeValue() : null;
if (sameSiteValue == null) {
return super.generateHeader(cookie, request);
}
Rfc6265CookieProcessor delegate = new Rfc6265CookieProcessor();
delegate.setSameSiteCookies(sameSite.attributeValue());
delegate.setSameSiteCookies(sameSiteValue);
return delegate.generateHeader(cookie, request);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -635,7 +635,10 @@ public void handleRequest(HttpServerExchange exchange) throws Exception {
private void beforeCommit(HttpServerExchange exchange) {
for (Cookie cookie : exchange.responseCookies()) {
SameSite sameSite = getSameSite(asServletCookie(cookie));
if (sameSite != null) {
if (sameSite == SameSite.OMITTED) {
cookie.setSameSite(false);
}
else if (sameSite != null) {
cookie.setSameSiteMode(sameSite.attributeValue());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ public void setPartitioned(Boolean partitioned) {
*/
public enum SameSite {

/**
* The SameSite cookie attribute will be omitted when creating the cookie.
*/
OMITTED(null),

/**
* Cookies are sent in both first-party and cross-origin requests.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -881,7 +881,7 @@ void sessionCookieConfiguration() {
}

@ParameterizedTest
@EnumSource
@EnumSource(mode = EnumSource.Mode.EXCLUDE, names = "OMITTED")
void sessionCookieSameSiteAttributeCanBeConfiguredAndOnlyAffectsSessionCookies(SameSite sameSite) throws Exception {
AbstractServletWebServerFactory factory = getFactory();
factory.getSession().getCookie().setSameSite(sameSite);
Expand All @@ -896,7 +896,7 @@ void sessionCookieSameSiteAttributeCanBeConfiguredAndOnlyAffectsSessionCookies(S
}

@ParameterizedTest
@EnumSource
@EnumSource(mode = EnumSource.Mode.EXCLUDE, names = "OMITTED")
void sessionCookieSameSiteAttributeCanBeConfiguredAndOnlyAffectsSessionCookiesWhenUsingCustomName(SameSite sameSite)
throws Exception {
AbstractServletWebServerFactory factory = getFactory();
Expand Down Expand Up @@ -949,6 +949,23 @@ void cookieSameSiteSuppliersShouldNotAffectSessionCookie() throws IOException, U
(header) -> assertThat(header).contains("test=test").contains("SameSite=Strict"));
}

@Test
void cookieSameSiteSuppliersShouldNotAffectOmittedSameSite() throws IOException, URISyntaxException {
AbstractServletWebServerFactory factory = getFactory();
factory.getSession().getCookie().setSameSite(SameSite.OMITTED);
factory.getSession().getCookie().setName("SESSIONCOOKIE");
factory.addCookieSameSiteSuppliers(CookieSameSiteSupplier.ofStrict());
factory.addInitializers(new ServletRegistrationBean<>(new CookieServlet(false), "/"));
this.webServer = factory.getWebServer();
this.webServer.start();
ClientHttpResponse clientResponse = getClientResponse(getLocalUrl("/"));
assertThat(clientResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
List<String> setCookieHeaders = clientResponse.getHeaders().get("Set-Cookie");
assertThat(setCookieHeaders).satisfiesExactlyInAnyOrder(
(header) -> assertThat(header).contains("SESSIONCOOKIE").doesNotContain("SameSite"),
(header) -> assertThat(header).contains("test=test").contains("SameSite=Strict"));
}

@Test
protected void sslSessionTracking() {
AbstractServletWebServerFactory factory = getFactory();
Expand Down