如何在 Spring Boot 中设置同站点 cookie 标志?
Posted
技术标签:
【中文标题】如何在 Spring Boot 中设置同站点 cookie 标志?【英文标题】:How to set same-site cookie flag in Spring Boot? 【发布时间】:2020-02-11 15:02:40 【问题描述】:是否可以在 Spring Boot 中设置Same-Site Cookie 标志?
我在 Chrome 中的问题:
如何解决这个问题呢?与http://google.com/ 的跨站点资源关联的 cookie 设置时没有
SameSite
属性。 Chrome 的未来版本 如果设置了跨站点请求,则只会传递 cookie 与SameSite=None
和Secure
。您可以在开发人员中查看 cookie 应用程序>存储>Cookies下的工具,并在以下位置查看更多详细信息 https://www.chromestatus.com/feature/5088147346030592 和 https://www.chromestatus.com/feature/5633521622188032.
【问题讨论】:
检查这个使用GenericFilterBean / 临时重定向请求来解决同类问题***.com/questions/63939078/… 【参考方案1】:这是 Spring Security 的一个未解决问题 (https://github.com/spring-projects/spring-security/issues/7537)
正如我在 Spring-Boot (2.1.7.RELEASE
) 中检查的那样,默认情况下它使用 DefaultCookieSerializer
,它带有一个属性 sameSite
,默认为 Lax
。
您可以在应用程序启动时通过以下代码进行修改。
注意:在真正的修复(配置)在下一个春季版本公开之前,这是一个 hack。
@Component
@AllArgsConstructor
public class SameSiteInjector
private final ApplicationContext applicationContext;
@EventListener
public void onApplicationEvent(ContextRefreshedEvent event)
DefaultCookieSerializer cookieSerializer = applicationContext.getBean(DefaultCookieSerializer.class);
log.info("Received DefaultCookieSerializer, Overriding SameSite Strict");
cookieSerializer.setSameSite("strict");
【讨论】:
如果我在项目中没有 spring-session 作为依赖项怎么办?只需添加 spring-session-core 依赖项和上面的代码 sn-p 就足够了,还是我应该使用另一种解决方法? @nik686 请参考以下一篇我发现在同一站点 cookie 部分中触及的文章avatao.com/Secure-development-with-Spring-Framework ***.com/questions/63939078/… 这样我就可以在没有任何额外依赖的情况下解决问题。【参考方案2】:当前版本的 Spring Boot (2.5.0-SNAPSHOT) 不支持 SameSite cookie 属性并且没有设置启用它。
Java Servlet 4.0 规范不支持 SameSite cookie 属性。打开javax.servlet.http.Cookiejava类就可以看到可用的属性了。
但是,有几个解决方法。您可以手动覆盖 Set-Cookie 属性。
第一种方法(使用自定义 Spring HttpFirewall)和围绕请求的包装器:
您需要在会话创建后立即包装请求并调整 cookie。您可以通过定义以下类来实现它:
一个bean(如果你想把所有东西都放在一个地方,你可以在SecurityConfig中定义它。为了简洁,我只是在上面放了@Component注解)
package hello.approach1;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.web.firewall.FirewalledRequest;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.RequestRejectedException;
import org.springframework.stereotype.Component;
@Component
public class CustomHttpFirewall implements HttpFirewall
@Override
public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException
return new RequestWrapper(request);
@Override
public HttpServletResponse getFirewalledResponse(HttpServletResponse response)
return new ResponseWrapper(response);
第一个包装类
package hello.approach1;
import java.util.Collection;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.http.HttpHeaders;
import org.springframework.security.web.firewall.FirewalledRequest;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
* Wrapper around HttpServletRequest that overwrites Set-Cookie response header and adds SameSite=None portion.
*/
public class RequestWrapper extends FirewalledRequest
/**
* Constructs a request object wrapping the given request.
*
* @param request The request to wrap
* @throws IllegalArgumentException if the request is null
*/
public RequestWrapper(HttpServletRequest request)
super(request);
/**
* Must be empty by default in Spring Boot. See FirewalledRequest.
*/
@Override
public void reset()
@Override
public HttpSession getSession(boolean create)
HttpSession session = super.getSession(create);
if (create)
ServletRequestAttributes ra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (ra != null)
overwriteSetCookie(ra.getResponse());
return session;
@Override
public String changeSessionId()
String newSessionId = super.changeSessionId();
ServletRequestAttributes ra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (ra != null)
overwriteSetCookie(ra.getResponse());
return newSessionId;
private void overwriteSetCookie(HttpServletResponse response)
if (response != null)
Collection<String> headers = response.getHeaders(HttpHeaders.SET_COOKIE);
boolean firstHeader = true;
for (String header : headers) // there can be multiple Set-Cookie attributes
if (firstHeader)
response.setHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=None")); // set
firstHeader = false;
continue;
response.addHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=None")); // add
第二个包装类
package hello.approach1;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
/**
* Dummy implementation.
* To be aligned with RequestWrapper.
*/
public class ResponseWrapper extends HttpServletResponseWrapper
/**
* Constructs a response adaptor wrapping the given response.
*
* @param response The response to be wrapped
* @throws IllegalArgumentException if the response is null
*/
public ResponseWrapper(HttpServletResponse response)
super(response);
第二种方式(使用Spring的AuthenticationSuccessHandler):
此方法不适用于基本身份验证。 在基本身份验证的情况下,在控制器返回响应对象之后,在调用 SameSiteFilter#addSameSiteCookieAttribute 之前,立即刷新/提交响应。
package hello.approach2;
import java.io.IOException;
import java.util.Collection;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException
addSameSiteCookieAttribute(response); // add SameSite=strict to Set-Cookie attribute
response.sendRedirect("/hello"); // redirect to hello.html after success auth
private void addSameSiteCookieAttribute(HttpServletResponse response)
Collection<String> headers = response.getHeaders(HttpHeaders.SET_COOKIE);
boolean firstHeader = true;
for (String header : headers) // there can be multiple Set-Cookie attributes
if (firstHeader)
response.setHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=Strict"));
firstHeader = false;
continue;
response.addHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=Strict"));
第三种方法(使用 javax.servlet.Filter):
此方法不适用于基本身份验证。 在基本身份验证的情况下,在控制器返回响应对象之后,在调用 SameSiteFilter#addSameSiteCookieAttribute 之前,立即刷新/提交响应。
package hello.approach3;
import java.io.IOException;
import java.util.Collection;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
public class SameSiteFilter implements javax.servlet.Filter
@Override
public void init(FilterConfig filterConfig) throws ServletException
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
chain.doFilter(request, response);
addSameSiteCookieAttribute((HttpServletResponse) response); // add SameSite=strict cookie attribute
private void addSameSiteCookieAttribute(HttpServletResponse response)
Collection<String> headers = response.getHeaders(HttpHeaders.SET_COOKIE);
boolean firstHeader = true;
for (String header : headers) // there can be multiple Set-Cookie attributes
if (firstHeader)
response.setHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=Strict"));
firstHeader = false;
continue;
response.addHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=Strict"));
@Override
public void destroy()
您可以查看 GitHub 上的this demo project,了解有关 org.springframework.security.web.authentication.AuthenticationSuccessHandler 或 javax.servlet.Filter 配置的更多详细信息。
SecurityConfig 包含所有必要的配置。
使用 addHeader 不能保证工作,因为基本上 Servlet 容器管理 Session 和 Cookie 的创建。为了 例如,如果您返回 JSON,第二种和第三种方法将不起作用 响应正文,因为应用程序服务器将覆盖 Set-Cookie 刷新响应期间的标头。但是,第二种和第三种方法将 在成功后将用户重定向到另一个页面的情况下工作 身份验证。
请注意 Postman 不呈现/支持 Cookies 部分下的 SameSite cookie 属性(至少在撰写本文时)。您可以查看 Set-Cookie 响应标头或使用 curl 来查看是否添加了 SameSite cookie 属性。
【讨论】:
方法 #1 有效(尽管在防火墙类中不需要ResponseWrapper
;只需return response;
)!感谢您的解决方案 - 让我们希望得到正确的实施,因为这比应有的复杂得多。【参考方案3】:
自上次更新以来,chrome 也开始向我显示该消息。不是关于 spring 的真正答案,但您可以将 cookie 标志添加到会话的标题中。就我而言,由于我使用的是 Spring Security,因此我打算在用户登录时添加它,因为我已经在操纵会话以添加身份验证数据。
有关更多信息,请查看类似主题的答案:https://***.com/a/43250133
要在用户登录后立即添加会话标头,您可以将代码基于此主题(通过创建实现 AuthenticationSuccessHandler 的 spring 组件):Spring Security. Redirect to protected page after authentication
【讨论】:
【参考方案4】:从 Spring Boot 版本 2.6.+ 开始,您可以通过编程方式或通过配置文件指定您的相同站点 cookie。
Spring boot 2.6.0 documentation
如果您想通过配置文件将 samesite 设置为 lax,则:
server.servlet.session.cookie.same-site=lax
或以编程方式
@Configuration
public class MySameSiteConfiguration
@Bean
public CookieSameSiteSupplier applicationCookieSameSiteSupplier()
return CookieSameSiteSupplier.ofLax();
【讨论】:
这应该是2022年的答案。Upper会导致Spring将属性绑定到org.springframework.boot.web.servlet.server.Session.cookie中,这会导致像TomcatServletWebServerFactory这样的servlet工厂类通过 configureCookieProcessor() 配置 CookieSameSiteSupplier。两者都创建 CookieSameSiteSupplier,servlet 工厂(例如 TomcatServletWebServerFactory)将使用它来生成会话 cookie。【参考方案5】:对我来说,以上都不起作用。我的问题是,登录后,使用本文中提到的其他方法创建的 SameSite 标志被重定向机制简单地忽略了。
在我们的 spring boot 2.4.4 应用程序中,我设法使用自定义 SameSiteHeaderWriter 完成了它:
import org.springframework.security.web.header.HeaderWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import static javax.ws.rs.core.HttpHeaders.SET_COOKIE;
/**
* This header writer just adds "SameSite=None;" to the Set-Cookie response header
*/
public class SameSiteHeaderWriter implements HeaderWriter
private static final String SAME_SITE_NONE = "SameSite=None";
private static final String SECURE = "Secure";
@Override
public void writeHeaders(HttpServletRequest request, HttpServletResponse response)
if (response.containsHeader(SET_COOKIE))
var setCookie = response.getHeader(SET_COOKIE);
var toAdd = new ArrayList<String>();
toAdd.add(setCookie);
if (! setCookie.contains(SAME_SITE_NONE))
toAdd.add(SAME_SITE_NONE);
if (! setCookie.contains(SECURE))
toAdd.add(SECURE);
response.setHeader(SET_COOKIE, String.join("; ", toAdd));
然后在我的 WebSecurityConfigurerAdapter#configure 中,我刚刚将此标头编写器添加到列表中:
if (corsEnabled)
httpSecurity = httpSecurity
.cors()
.and()
.headers(configurer ->
configurer.frameOptions().disable();
configurer.addHeaderWriter(new SameSiteHeaderWriter());
);
用户必须在知道风险的情况下在我们的应用中明确启用此功能。
只是认为这可能对将来的某人有所帮助。
【讨论】:
此解决方案适用于 Spring boot 1.4 我的服务器将用户重定向到另一个关于成功验证的页面,以上方法对我来说非常有效。即使在重定向的情况下,服务器也应该在包含新位置(重定向位置)的响应中包含 cookie。如果您提供代码示例,我可以查看您的配置并调查为什么上述方法不适合您。【参考方案6】:从 Spring Boot 2.6.0 开始,这现在可以轻松实现:
import org.springframework.http.ResponseCookie;
ResponseCookie springCookie = ResponseCookie.from("refresh-token", "000")
.sameSite("Strict")
.build();
并在ResponseEntity
中返回它,可能是这样的:
ResponseEntity
.ok()
.header(HttpHeaders.SET_COOKIE, springCookie.toString())
.build();
【讨论】:
【参考方案7】:按照文档解决此问题: https://github.com/GoogleChromeLabs/samesite-examples
它有不同语言的例子
【讨论】:
很遗憾,该网站似乎没有任何与此特定问题相关的条目(至少截至 2020 年 4 月 2 日)。 谢谢。只需要禁用同一个站点所需的标志。 2-3小时的调试终于解决了。以上是关于如何在 Spring Boot 中设置同站点 cookie 标志?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Spring Boot 中设置 UTF-8 字符编码?