Support for sameSite attribute in WebFlux

Bypass server cookie and write Set-Cookie header directly for Reactor
Netty, and Servlet API which do not provide options.

For Undertow use the sameSite attribute.

Closes gh-23693
This commit is contained in:
Rossen Stoyanchev
2019-09-25 17:16:21 +01:00
parent b1ed0511f7
commit 17c423f5af
7 changed files with 174 additions and 66 deletions

View File

@@ -16,12 +16,13 @@
package org.springframework.http;
import java.time.Duration;
import java.util.Arrays;
import org.hamcrest.Matchers;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
/**
* Unit tests for {@link ResponseCookie}.
@@ -30,40 +31,60 @@ import static org.junit.Assert.*;
public class ResponseCookieTests {
@Test
public void defaultValues() {
public void basic() {
assertEquals("id=", ResponseCookie.from("id", null).build().toString());
assertEquals("id=1fWa", ResponseCookie.from("id", "1fWa").build().toString());
assertEquals(
"id=1fWa; Path=/path; Domain=abc; " +
"Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; " +
"Secure; HttpOnly; SameSite=None",
ResponseCookie.from("id", "1fWa")
.domain("abc").path("/path").maxAge(0).httpOnly(true).secure(true).sameSite("None")
.build().toString());
}
@Test
public void httpOnlyStrictSecureWithDomainAndPath() {
assertEquals("id=1fWa; Path=/projects; Domain=spring.io; Secure; HttpOnly; SameSite=strict",
ResponseCookie.from("id", "1fWa").domain("spring.io").path("/projects")
.httpOnly(true).secure(true).sameSite("strict").build().toString());
public void nameChecks() {
Arrays.asList("id", "i.d.", "i-d", "+id", "i*d", "i$d", "#id")
.forEach(name -> {
ResponseCookie.from(name, "value").build();
// no exception..
});
Arrays.asList("\"id\"", "id\t", "i\td", "i d", "i;d", "{id}", "[id]", "\"", "id\u0091")
.forEach(name -> {
try {
ResponseCookie.from(name, "value").build();
}
catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(), Matchers.containsString("RFC2616 token"));
}
});
}
@Test
public void maxAge() {
public void valueChecks() {
Duration maxAge = Duration.ofDays(365);
String expires = HttpHeaders.formatDate(System.currentTimeMillis() + maxAge.toMillis());
expires = expires.substring(0, expires.indexOf(":") + 1);
Arrays.asList("1fWa", "", null, "1f=Wa", "1f-Wa", "1f/Wa", "1.f.W.a.")
.forEach(value -> {
ResponseCookie.from("id", value).build();
// no exception..
});
assertThat(ResponseCookie.from("id", "1fWa").maxAge(maxAge).build().toString(), allOf(
startsWith("id=1fWa; Max-Age=31536000; Expires=" + expires),
endsWith(" GMT")));
assertThat(ResponseCookie.from("id", "1fWa").maxAge(maxAge.getSeconds()).build().toString(), allOf(
startsWith("id=1fWa; Max-Age=31536000; Expires=" + expires),
endsWith(" GMT")));
Arrays.asList("1f\tWa", "\t", "1f Wa", "1f;Wa", "\"1fWa", "1f\\Wa", "1f\"Wa", "\"", "1fWa\u0005", "1f\u0091Wa")
.forEach(value -> {
try {
ResponseCookie.from("id", value).build();
}
catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(), Matchers.containsString("RFC2616 cookie value"));
}
});
}
@Test
public void maxAge0() {
assertEquals("id=1fWa; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT",
ResponseCookie.from("id", "1fWa").maxAge(Duration.ofSeconds(0)).build().toString());
assertEquals("id=1fWa; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT",
ResponseCookie.from("id", "1fWa").maxAge(0).build().toString());
}
}

View File

@@ -18,7 +18,7 @@ package org.springframework.mock.http.server.reactive.test;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
@@ -32,6 +32,7 @@ import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.http.server.reactive.AbstractServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.util.MimeType;
@@ -101,8 +102,11 @@ public class MockServerHttpResponse extends AbstractServerHttpResponse {
@Override
protected void applyCookies() {
getCookies().values().stream().flatMap(Collection::stream)
.forEach(cookie -> getHeaders().add(HttpHeaders.SET_COOKIE, cookie.toString()));
for (List<ResponseCookie> cookies : getCookies().values()) {
for (ResponseCookie cookie : cookies) {
getHeaders().add(HttpHeaders.SET_COOKIE, cookie.toString());
}
}
}
@Override