From 636b99330c5f96c16036f7b78d1a890bc32e16f2 Mon Sep 17 00:00:00 2001 From: Vedran Pavic Date: Wed, 6 Jul 2022 23:37:13 +0200 Subject: [PATCH 1/2] Simplify creation of CookieLocaleResolver with custom cookie name At present, creating CookieLocaleResolver with custom cookie name requires creating an instance using the default constructor and then using setter to configure the desired cookie name. This commit introduces CookieLocaleResolver constructor that accepts cookie name thus allowing for a simpler creation of an instance with the desired cookie name. --- .../web/servlet/i18n/CookieLocaleResolver.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java index 0572bc70b7ad..50f339a43514 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java @@ -104,12 +104,23 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte private Function defaultTimeZoneFunction = request -> getDefaultTimeZone(); + + /** + * Create a new instance of {@link CookieLocaleResolver} using the supplied + * cookie name. + * @param cookieName the cookie name + * @since 6.0 + */ + public CookieLocaleResolver(String cookieName) { + setCookieName(cookieName); + } + /** * Create a new instance of {@link CookieLocaleResolver} using the * {@linkplain #DEFAULT_COOKIE_NAME default cookie name}. */ public CookieLocaleResolver() { - setCookieName(DEFAULT_COOKIE_NAME); + this(DEFAULT_COOKIE_NAME); } From fc567e14f0adc3dec5ae204d3f8b426b66083f6b Mon Sep 17 00:00:00 2001 From: Vedran Pavic Date: Wed, 6 Jul 2022 23:50:20 +0200 Subject: [PATCH 2/2] Refactor CookieLocaleResolver At present, CookieLocaleResolver extends CookieGenerator instead of AbstractLocale(Context)Resolver like other LocaleResolver implementations. This means it duplicates some common aspects of LocaleResolver hierarchy while also exposing some CookieGenerator operations, such as #addCookie and #removeCookie. Additionally, CookieGenerator's support for writing cookies is based on Servlet support which at current baseline doesn't support SameSite directive. This commit refactors CookieLocaleResolver to make it extend AbstractLocaleContextResolver and also replaces CookieGenerator's cookie writing support with newer and more capable ResponseCookie. --- .../web/util/CookieGenerator.java | 5 +- .../servlet/i18n/CookieLocaleResolver.java | 248 ++++++++++++------ .../i18n/CookieLocaleResolverTests.java | 37 +-- .../web/servlet/i18n/LocaleResolverTests.java | 4 + 4 files changed, 189 insertions(+), 105 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/util/CookieGenerator.java b/spring-web/src/main/java/org/springframework/web/util/CookieGenerator.java index 82cc13d9eac6..1173bae9f0b6 100644 --- a/spring-web/src/main/java/org/springframework/web/util/CookieGenerator.java +++ b/spring-web/src/main/java/org/springframework/web/util/CookieGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -30,13 +30,12 @@ * given response. * *

Can serve as base class for components that generate specific cookies, - * such as CookieLocaleResolver and CookieThemeResolver. + * such as CookieThemeResolver. * * @author Juergen Hoeller * @since 1.1.4 * @see #addCookie * @see #removeCookie - * @see org.springframework.web.servlet.i18n.CookieLocaleResolver * @see org.springframework.web.servlet.theme.CookieThemeResolver */ public class CookieGenerator { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java index 50f339a43514..4a1cc9c8ab0c 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java @@ -16,6 +16,7 @@ package org.springframework.web.servlet.i18n; +import java.time.Duration; import java.util.Locale; import java.util.TimeZone; import java.util.function.Function; @@ -23,16 +24,17 @@ import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.context.i18n.LocaleContext; -import org.springframework.context.i18n.SimpleLocaleContext; import org.springframework.context.i18n.TimeZoneAwareLocaleContext; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseCookie; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import org.springframework.web.servlet.LocaleContextResolver; import org.springframework.web.servlet.LocaleResolver; -import org.springframework.web.util.CookieGenerator; import org.springframework.web.util.WebUtils; /** @@ -57,7 +59,7 @@ * @see #setDefaultLocale * @see #setDefaultTimeZone */ -public class CookieLocaleResolver extends CookieGenerator implements LocaleContextResolver { +public class CookieLocaleResolver extends AbstractLocaleContextResolver { /** * The name of the request attribute that holds the {@code Locale}. @@ -86,16 +88,39 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte */ public static final String DEFAULT_COOKIE_NAME = CookieLocaleResolver.class.getName() + ".LOCALE"; + /** + * The default cookie max age (persists until browser shutdown) if none is + * explicitly set. + */ + public static final Duration DEFAULT_COOKIE_MAX_AGE = Duration.ofSeconds(-1); - private boolean languageTagCompliant = true; + /** + * The default cookie path used if none is explicitly set. + */ + public static final String DEFAULT_COOKIE_PATH = "/"; - private boolean rejectInvalidCookies = true; + private static final Log logger = LogFactory.getLog(CookieLocaleResolver.class); + + + private String cookieName; + + private Duration cookieMaxAge = DEFAULT_COOKIE_MAX_AGE; @Nullable - private Locale defaultLocale; + private String cookiePath = DEFAULT_COOKIE_PATH; @Nullable - private TimeZone defaultTimeZone; + private String cookieDomain; + + private boolean cookieSecure; + + private boolean cookieHttpOnly; + + private String cookieSameSite = "Lax"; + + private boolean languageTagCompliant = true; + + private boolean rejectInvalidCookies = true; private Function defaultLocaleFunction = request -> { Locale defaultLocale = getDefaultLocale(); @@ -112,7 +137,8 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte * @since 6.0 */ public CookieLocaleResolver(String cookieName) { - setCookieName(cookieName); + Assert.notNull(cookieName, "cookieName must not be null"); + this.cookieName = cookieName; } /** @@ -124,6 +150,78 @@ public CookieLocaleResolver() { } + /** + * Set the name of cookie created by this resolver. + * @param cookieName the cookie name + * @deprecated since 6.0, in favor of {@link #CookieLocaleResolver(String)} + */ + @Deprecated + public void setCookieName(String cookieName) { + Assert.notNull(cookieName, "cookieName must not be null"); + this.cookieName = cookieName; + } + + /** + * Set the cookie "Max-Age" attribute. + * @since 6.0 + * @see org.springframework.http.ResponseCookie.ResponseCookieBuilder#maxAge(Duration) + */ + public void setCookieMaxAge(Duration cookieMaxAge) { + Assert.notNull(cookieMaxAge, "cookieMaxAge must not be null"); + this.cookieMaxAge = cookieMaxAge; + } + + /** + * Variant of {@link #setCookieMaxAge(Duration)} accepting a value in + * seconds. + */ + @Deprecated + public void setCookieMaxAge(@Nullable Integer cookieMaxAge) { + setCookieMaxAge(Duration.ofSeconds((cookieMaxAge != null) ? cookieMaxAge : -1)); + } + + /** + * Set the cookie "Path" attribute. + * @see org.springframework.http.ResponseCookie.ResponseCookieBuilder#path(String) + */ + public void setCookiePath(@Nullable String cookiePath) { + this.cookiePath = cookiePath; + } + + /** + * Set the cookie "Domain" attribute. + * @see org.springframework.http.ResponseCookie.ResponseCookieBuilder#domain(String) + */ + public void setCookieDomain(@Nullable String cookieDomain) { + this.cookieDomain = cookieDomain; + } + + /** + * Add the "Secure" attribute to the cookie. + * @see org.springframework.http.ResponseCookie.ResponseCookieBuilder#secure(boolean) + */ + public void setCookieSecure(boolean cookieSecure) { + this.cookieSecure = cookieSecure; + } + + /** + * Add the "HttpOnly" attribute to the cookie. + * @see org.springframework.http.ResponseCookie.ResponseCookieBuilder#httpOnly(boolean) + */ + public void setCookieHttpOnly(boolean cookieHttpOnly) { + this.cookieHttpOnly = cookieHttpOnly; + } + + /** + * Add the "SameSite" attribute to the cookie. + * @since 6.0 + * @see org.springframework.http.ResponseCookie.ResponseCookieBuilder#sameSite(String) + */ + public void setCookieSameSite(String cookieSameSite) { + Assert.notNull(cookieSameSite, "cookieSameSite must not be null"); + this.cookieSameSite = cookieSameSite; + } + /** * Specify whether this resolver's cookies should be compliant with BCP 47 * language tags instead of Java's legacy locale specification format. @@ -172,40 +270,6 @@ public boolean isRejectInvalidCookies() { return this.rejectInvalidCookies; } - /** - * Set a fixed locale that this resolver will return if no cookie is found. - */ - public void setDefaultLocale(@Nullable Locale defaultLocale) { - this.defaultLocale = defaultLocale; - } - - /** - * Return the fixed locale that this resolver will return if no cookie is found, - * if any. - */ - @Nullable - protected Locale getDefaultLocale() { - return this.defaultLocale; - } - - /** - * Set a fixed time zone that this resolver will return if no cookie is found. - * @since 4.0 - */ - public void setDefaultTimeZone(@Nullable TimeZone defaultTimeZone) { - this.defaultTimeZone = defaultTimeZone; - } - - /** - * Return the fixed time zone that this resolver will return if no cookie is found, - * if any. - * @since 4.0 - */ - @Nullable - protected TimeZone getDefaultTimeZone() { - return this.defaultTimeZone; - } - /** * Set the function used to determine the default locale for the given request, * called if no locale cookie has been found. @@ -267,46 +331,43 @@ private void parseLocaleCookieIfNecessary(HttpServletRequest request) { TimeZone timeZone = null; // Retrieve and parse cookie value. - String cookieName = getCookieName(); - if (cookieName != null) { - Cookie cookie = WebUtils.getCookie(request, cookieName); - if (cookie != null) { - String value = cookie.getValue(); - String localePart = value; - String timeZonePart = null; - int separatorIndex = localePart.indexOf('/'); - if (separatorIndex == -1) { - // Leniently accept older cookies separated by a space... - separatorIndex = localePart.indexOf(' '); - } - if (separatorIndex >= 0) { - localePart = value.substring(0, separatorIndex); - timeZonePart = value.substring(separatorIndex + 1); + Cookie cookie = WebUtils.getCookie(request, this.cookieName); + if (cookie != null) { + String value = cookie.getValue(); + String localePart = value; + String timeZonePart = null; + int separatorIndex = localePart.indexOf('/'); + if (separatorIndex == -1) { + // Leniently accept older cookies separated by a space... + separatorIndex = localePart.indexOf(' '); + } + if (separatorIndex >= 0) { + localePart = value.substring(0, separatorIndex); + timeZonePart = value.substring(separatorIndex + 1); + } + try { + locale = (!"-".equals(localePart) ? parseLocaleValue(localePart) : null); + if (timeZonePart != null) { + timeZone = StringUtils.parseTimeZoneString(timeZonePart); } - try { - locale = (!"-".equals(localePart) ? parseLocaleValue(localePart) : null); - if (timeZonePart != null) { - timeZone = StringUtils.parseTimeZoneString(timeZonePart); - } + } + catch (IllegalArgumentException ex) { + if (isRejectInvalidCookies() && + request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) { + throw new IllegalStateException("Encountered invalid locale cookie '" + + this.cookieName + "': [" + value + "] due to: " + ex.getMessage()); } - catch (IllegalArgumentException ex) { - if (isRejectInvalidCookies() && - request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) { - throw new IllegalStateException("Encountered invalid locale cookie '" + - cookieName + "': [" + value + "] due to: " + ex.getMessage()); - } - else { - // Lenient handling (e.g. error dispatch): ignore locale/timezone parse exceptions - if (logger.isDebugEnabled()) { - logger.debug("Ignoring invalid locale cookie '" + cookieName + - "': [" + value + "] due to: " + ex.getMessage()); - } + else { + // Lenient handling (e.g. error dispatch): ignore locale/timezone parse exceptions + if (logger.isDebugEnabled()) { + logger.debug("Ignoring invalid locale cookie '" + this.cookieName + + "': [" + value + "] due to: " + ex.getMessage()); } } - if (logger.isTraceEnabled()) { - logger.trace("Parsed cookie value [" + cookie.getValue() + "] into locale '" + locale + - "'" + (timeZone != null ? " and time zone '" + timeZone.getID() + "'" : "")); - } + } + if (logger.isTraceEnabled()) { + logger.trace("Parsed cookie value [" + cookie.getValue() + "] into locale '" + locale + + "'" + (timeZone != null ? " and time zone '" + timeZone.getID() + "'" : "")); } } @@ -317,11 +378,6 @@ private void parseLocaleCookieIfNecessary(HttpServletRequest request) { } } - @Override - public void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale) { - setLocaleContext(request, response, (locale != null ? new SimpleLocaleContext(locale) : null)); - } - @Override public void setLocaleContext(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable LocaleContext localeContext) { @@ -330,17 +386,35 @@ public void setLocaleContext(HttpServletRequest request, @Nullable HttpServletRe Locale locale = null; TimeZone timeZone = null; + ResponseCookie cookie; if (localeContext != null) { locale = localeContext.getLocale(); if (localeContext instanceof TimeZoneAwareLocaleContext timeZoneAwareLocaleContext) { timeZone = timeZoneAwareLocaleContext.getTimeZone(); } - addCookie(response, - (locale != null ? toLocaleValue(locale) : "-") + (timeZone != null ? '/' + timeZone.getID() : "")); + cookie = ResponseCookie.from(this.cookieName, + (locale != null ? toLocaleValue(locale) : "-") + + (timeZone != null ? '/' + timeZone.getID() : "")) + .maxAge(this.cookieMaxAge) + .path(this.cookiePath) + .domain(this.cookieDomain) + .secure(this.cookieSecure) + .httpOnly(this.cookieHttpOnly) + .sameSite(this.cookieSameSite) + .build(); } else { - removeCookie(response); + // a cookie with empty value and max age 0 + cookie = ResponseCookie.from(this.cookieName, "") + .maxAge(Duration.ZERO) + .path(this.cookiePath) + .domain(this.cookieDomain) + .secure(this.cookieSecure) + .httpOnly(this.cookieHttpOnly) + .sameSite(this.cookieSameSite) + .build(); } + response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME, (locale != null ? locale : this.defaultLocaleFunction.apply(request))); request.setAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME, diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/CookieLocaleResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/CookieLocaleResolverTests.java index 7cc0467b2af5..e2dfd3edd9df 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/CookieLocaleResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/CookieLocaleResolverTests.java @@ -16,6 +16,7 @@ package org.springframework.web.servlet.i18n; +import java.time.Duration; import java.util.Locale; import java.util.TimeZone; @@ -27,6 +28,8 @@ import org.springframework.context.i18n.SimpleLocaleContext; import org.springframework.context.i18n.SimpleTimeZoneAwareLocaleContext; import org.springframework.context.i18n.TimeZoneAwareLocaleContext; +import org.springframework.http.HttpHeaders; +import org.springframework.web.testfixture.servlet.MockCookie; import org.springframework.web.testfixture.servlet.MockHttpServletRequest; import org.springframework.web.testfixture.servlet.MockHttpServletResponse; import org.springframework.web.util.WebUtils; @@ -57,7 +60,7 @@ void resolveLocale() { Cookie cookie = new Cookie("LanguageKoekje", "nl"); request.setCookies(cookie); - resolver.setCookieName("LanguageKoekje"); + resolver = new CookieLocaleResolver("LanguageKoekje"); Locale loc = resolver.resolveLocale(request); assertThat(loc.getLanguage()).isEqualTo("nl"); } @@ -67,7 +70,7 @@ void resolveLocaleContext() { Cookie cookie = new Cookie("LanguageKoekje", "nl"); request.setCookies(cookie); - resolver.setCookieName("LanguageKoekje"); + resolver = new CookieLocaleResolver("LanguageKoekje"); LocaleContext loc = resolver.resolveLocaleContext(request); assertThat(loc.getLocale().getLanguage()).isEqualTo("nl"); assertThat(loc).isInstanceOf(TimeZoneAwareLocaleContext.class); @@ -79,7 +82,7 @@ void resolveLocaleContextWithTimeZone() { Cookie cookie = new Cookie("LanguageKoekje", "nl GMT+1"); request.setCookies(cookie); - resolver.setCookieName("LanguageKoekje"); + resolver = new CookieLocaleResolver("LanguageKoekje"); LocaleContext loc = resolver.resolveLocaleContext(request); assertThat(loc.getLocale().getLanguage()).isEqualTo("nl"); assertThat(loc).isInstanceOf(TimeZoneAwareLocaleContext.class); @@ -91,7 +94,7 @@ void resolveLocaleContextWithInvalidLocale() { Cookie cookie = new Cookie("LanguageKoekje", "++ GMT+1"); request.setCookies(cookie); - resolver.setCookieName("LanguageKoekje"); + resolver = new CookieLocaleResolver("LanguageKoekje"); assertThatIllegalStateException().isThrownBy(() -> resolver.resolveLocaleContext(request)) .withMessageContaining("LanguageKoekje") .withMessageContaining("++ GMT+1"); @@ -104,8 +107,8 @@ void resolveLocaleContextWithInvalidLocaleOnErrorDispatch() { Cookie cookie = new Cookie("LanguageKoekje", "++ GMT+1"); request.setCookies(cookie); + resolver = new CookieLocaleResolver("LanguageKoekje"); resolver.setDefaultTimeZone(TimeZone.getTimeZone("GMT+2")); - resolver.setCookieName("LanguageKoekje"); LocaleContext loc = resolver.resolveLocaleContext(request); assertThat(loc.getLocale()).isEqualTo(Locale.GERMAN); assertThat(loc).isInstanceOf(TimeZoneAwareLocaleContext.class); @@ -117,7 +120,7 @@ void resolveLocaleContextWithInvalidTimeZone() { Cookie cookie = new Cookie("LanguageKoekje", "nl X-MT"); request.setCookies(cookie); - resolver.setCookieName("LanguageKoekje"); + resolver = new CookieLocaleResolver("LanguageKoekje"); assertThatIllegalStateException().isThrownBy(() -> resolver.resolveLocaleContext(request)) .withMessageContaining("LanguageKoekje") .withMessageContaining("nl X-MT"); @@ -129,8 +132,8 @@ void resolveLocaleContextWithInvalidTimeZoneOnErrorDispatch() { Cookie cookie = new Cookie("LanguageKoekje", "nl X-MT"); request.setCookies(cookie); + resolver = new CookieLocaleResolver("LanguageKoekje"); resolver.setDefaultTimeZone(TimeZone.getTimeZone("GMT+2")); - resolver.setCookieName("LanguageKoekje"); LocaleContext loc = resolver.resolveLocaleContext(request); assertThat(loc.getLocale().getLanguage()).isEqualTo("nl"); assertThat(loc).isInstanceOf(TimeZoneAwareLocaleContext.class); @@ -141,12 +144,13 @@ void resolveLocaleContextWithInvalidTimeZoneOnErrorDispatch() { void setAndResolveLocale() { resolver.setLocale(request, response, new Locale("nl", "")); - Cookie cookie = response.getCookie(CookieLocaleResolver.DEFAULT_COOKIE_NAME); + MockCookie cookie = MockCookie.parse(response.getHeader(HttpHeaders.SET_COOKIE)); assertThat(cookie).isNotNull(); assertThat(cookie.getName()).isEqualTo(CookieLocaleResolver.DEFAULT_COOKIE_NAME); assertThat(cookie.getDomain()).isNull(); assertThat(cookie.getPath()).isEqualTo(CookieLocaleResolver.DEFAULT_COOKIE_PATH); assertThat(cookie.getSecure()).isFalse(); + assertThat(cookie.getSameSite()).isEqualTo("Lax"); request = new MockHttpServletRequest(); request.setCookies(cookie); @@ -208,12 +212,13 @@ void setAndResolveLocaleContextWithTimeZoneOnly() { void setAndResolveLocaleWithCountry() { resolver.setLocale(request, response, new Locale("de", "AT")); - Cookie cookie = response.getCookie(CookieLocaleResolver.DEFAULT_COOKIE_NAME); + MockCookie cookie = MockCookie.parse(response.getHeader(HttpHeaders.SET_COOKIE)); assertThat(cookie).isNotNull(); assertThat(cookie.getName()).isEqualTo(CookieLocaleResolver.DEFAULT_COOKIE_NAME); assertThat(cookie.getDomain()).isNull(); assertThat(cookie.getPath()).isEqualTo(CookieLocaleResolver.DEFAULT_COOKIE_PATH); assertThat(cookie.getSecure()).isFalse(); + assertThat(cookie.getSameSite()).isEqualTo("Lax"); assertThat(cookie.getValue()).isEqualTo("de-AT"); request = new MockHttpServletRequest(); @@ -230,12 +235,13 @@ void setAndResolveLocaleWithCountryAsLegacyJava() { resolver.setLanguageTagCompliant(false); resolver.setLocale(request, response, new Locale("de", "AT")); - Cookie cookie = response.getCookie(CookieLocaleResolver.DEFAULT_COOKIE_NAME); + MockCookie cookie = MockCookie.parse(response.getHeader(HttpHeaders.SET_COOKIE)); assertThat(cookie).isNotNull(); assertThat(cookie.getName()).isEqualTo(CookieLocaleResolver.DEFAULT_COOKIE_NAME); assertThat(cookie.getDomain()).isNull(); assertThat(cookie.getPath()).isEqualTo(CookieLocaleResolver.DEFAULT_COOKIE_PATH); assertThat(cookie.getSecure()).isFalse(); + assertThat(cookie.getSameSite()).isEqualTo("Lax"); assertThat(cookie.getValue()).isEqualTo("de_AT"); request = new MockHttpServletRequest(); @@ -249,26 +255,27 @@ void setAndResolveLocaleWithCountryAsLegacyJava() { @Test void customCookie() { - resolver.setCookieName("LanguageKoek"); + resolver = new CookieLocaleResolver("LanguageKoek"); resolver.setCookieDomain(".springframework.org"); resolver.setCookiePath("/mypath"); - resolver.setCookieMaxAge(10000); + resolver.setCookieMaxAge(Duration.ofSeconds(10000)); resolver.setCookieSecure(true); + resolver.setCookieSameSite("Lax"); resolver.setLocale(request, response, new Locale("nl", "")); - Cookie cookie = response.getCookie("LanguageKoek"); + MockCookie cookie = MockCookie.parse(response.getHeader(HttpHeaders.SET_COOKIE)); assertThat(cookie).isNotNull(); assertThat(cookie.getName()).isEqualTo("LanguageKoek"); assertThat(cookie.getDomain()).isEqualTo(".springframework.org"); assertThat(cookie.getPath()).isEqualTo("/mypath"); assertThat(cookie.getMaxAge()).isEqualTo(10000); assertThat(cookie.getSecure()).isTrue(); + assertThat(cookie.getSameSite()).isEqualTo("Lax"); request = new MockHttpServletRequest(); request.setCookies(cookie); - resolver = new CookieLocaleResolver(); - resolver.setCookieName("LanguageKoek"); + resolver = new CookieLocaleResolver("LanguageKoek"); Locale loc = resolver.resolveLocale(request); assertThat(loc.getLanguage()).isEqualTo("nl"); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/LocaleResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/LocaleResolverTests.java index a6fd5a187e62..3888670e3209 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/LocaleResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/LocaleResolverTests.java @@ -99,6 +99,8 @@ private void doTest(LocaleResolver localeResolver, boolean shouldSet) { if (localeContextResolver instanceof AbstractLocaleContextResolver) { ((AbstractLocaleContextResolver) localeContextResolver).setDefaultTimeZone(TimeZone.getTimeZone("GMT+1")); + request.removeAttribute(CookieLocaleResolver.LOCALE_REQUEST_ATTRIBUTE_NAME); + localeContextResolver.resolveLocaleContext(request); assertThat(TimeZone.getTimeZone("GMT+1")).isEqualTo(((TimeZoneAwareLocaleContext) localeContext).getTimeZone()); } @@ -134,6 +136,8 @@ private void doTest(LocaleResolver localeResolver, boolean shouldSet) { if (localeContextResolver instanceof AbstractLocaleContextResolver) { ((AbstractLocaleContextResolver) localeContextResolver).setDefaultLocale(Locale.GERMANY); + request.removeAttribute(CookieLocaleResolver.LOCALE_REQUEST_ATTRIBUTE_NAME); + localeContextResolver.resolveLocaleContext(request); assertThat(localeContext.getLocale()).isEqualTo(Locale.GERMANY); } }