diff --git a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java index 9300d532760..c33cbedfb41 100644 --- a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java +++ b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java @@ -1218,9 +1218,14 @@ public class HttpHeaders implements MultiValueMap, Serializable * @see #setZonedDateTime(String, ZonedDateTime) */ public void setDate(String headerName, long date) { + set(headerName, formatDate(date)); + } + + // Package private: also used in ResponseCookie.. + static String formatDate(long date) { Instant instant = Instant.ofEpochMilli(date); - ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, GMT); - set(headerName, DATE_FORMATTERS[0].format(zonedDateTime)); + ZonedDateTime time = ZonedDateTime.ofInstant(instant, GMT); + return DATE_FORMATTERS[0].format(time); } /** diff --git a/spring-web/src/main/java/org/springframework/http/ResponseCookie.java b/spring-web/src/main/java/org/springframework/http/ResponseCookie.java index 6c44f596ca3..7a582def77f 100644 --- a/spring-web/src/main/java/org/springframework/http/ResponseCookie.java +++ b/spring-web/src/main/java/org/springframework/http/ResponseCookie.java @@ -154,12 +154,10 @@ public final class ResponseCookie extends HttpCookie { sb.append("; Domain=").append(this.domain); } if (!this.maxAge.isNegative()) { - sb.append("; Max-Age=").append(this.maxAge); + sb.append("; Max-Age=").append(this.maxAge.getSeconds()); sb.append("; Expires="); - HttpHeaders headers = new HttpHeaders(); - long seconds = this.maxAge.getSeconds(); - headers.setExpires(seconds > 0 ? System.currentTimeMillis() + seconds : 0); - sb.append(headers.getFirst(HttpHeaders.EXPIRES)); + long millis = this.maxAge.getSeconds() > 0 ? System.currentTimeMillis() + this.maxAge.toMillis() : 0; + sb.append(HttpHeaders.formatDate(millis)); } if (this.secure) { sb.append("; Secure"); @@ -267,7 +265,7 @@ public final class ResponseCookie extends HttpCookie { ResponseCookieBuilder maxAge(Duration maxAge); /** - * Set the cookie "Max-Age" attribute in seconds. + * Variant of {@link #maxAge(Duration)} accepting a value in seconds. */ ResponseCookieBuilder maxAge(long maxAgeSeconds); @@ -294,6 +292,9 @@ public final class ResponseCookie extends HttpCookie { /** * Add the "SameSite" attribute to the cookie. + *

This limits the scope of the cookie such that it will only be + * attached to same site requests if {@code "Strict"} or cross-site + * requests if {@code "Lax"}. * @see RFC6265 bis */ ResponseCookieBuilder sameSite(String sameSite); diff --git a/spring-web/src/main/java/org/springframework/web/server/session/CookieWebSessionIdResolver.java b/spring-web/src/main/java/org/springframework/web/server/session/CookieWebSessionIdResolver.java index 809552bfdb0..10e110d535a 100644 --- a/spring-web/src/main/java/org/springframework/web/server/session/CookieWebSessionIdResolver.java +++ b/spring-web/src/main/java/org/springframework/web/server/session/CookieWebSessionIdResolver.java @@ -92,7 +92,7 @@ public class CookieWebSessionIdResolver implements WebSessionIdResolver { * Return the configured "SameSite" attribute value for the session cookie. */ public String getSameSite() { - return sameSite; + return this.sameSite; } @Override diff --git a/spring-web/src/test/java/org/springframework/http/ResponseCookieTests.java b/spring-web/src/test/java/org/springframework/http/ResponseCookieTests.java new file mode 100644 index 00000000000..41ef9edc3f1 --- /dev/null +++ b/spring-web/src/test/java/org/springframework/http/ResponseCookieTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2018 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.http; + +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +/** + * Unit tests for {@link ResponseCookie}. + * @author Rossen Stoyanchev + */ +public class ResponseCookieTests { + + @Test + public void defaultValues() { + assertEquals("id=1fWa", ResponseCookie.from("id", "1fWa").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()); + } + + @Test + public void maxAge() { + + Duration maxAge = Duration.ofDays(365); + String expires = HttpHeaders.formatDate(System.currentTimeMillis() + maxAge.toMillis()); + expires = expires.substring(0, expires.indexOf(":") + 1); + + 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"))); + } + + @Test + public void maxAge0() { + assertEquals("id=1fWa; Max-Age=0; Expires=Thu, 1 Jan 1970 00:00:00 GMT", + ResponseCookie.from("id", "1fWa").maxAge(Duration.ofSeconds(0)).build().toString()); + + assertEquals("id=1fWa; Max-Age=0; Expires=Thu, 1 Jan 1970 00:00:00 GMT", + ResponseCookie.from("id", "1fWa").maxAge(0).build().toString()); + } + +} diff --git a/spring-web/src/test/java/org/springframework/web/server/session/CookieWebSessionIdResolverTests.java b/spring-web/src/test/java/org/springframework/web/server/session/CookieWebSessionIdResolverTests.java index 47a41495a21..f710ef4cdc9 100644 --- a/spring-web/src/test/java/org/springframework/web/server/session/CookieWebSessionIdResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/server/session/CookieWebSessionIdResolverTests.java @@ -35,7 +35,7 @@ public class CookieWebSessionIdResolverTests { @Test - public void setSessionId() throws Exception { + public void setSessionId() { MockServerHttpRequest request = MockServerHttpRequest.get("https://example.org/path").build(); MockServerWebExchange exchange = MockServerWebExchange.from(request); this.resolver.setSessionId(exchange, "123");