Cookie 是 Web 开发里最古老也最常用的状态保持手段,登录会话、埋点标识、主题偏好都离不开它。不过在 Spring Boot 项目里,Cookie 的操作方式和普通 Java Web 有一些细节差异,尤其是 Chrome 从 80 版本开始默认把未声明 SameSite 的 Cookie 按 Lax 处理之后,跨站场景下 Cookie 不携带的问题频频出现。这篇文章把 Cookie 的写入、读取、属性配置和 SameSite 的全局设置一次性讲清楚。

一、在 Spring Boot 中设置 Cookie 的两种方式
第一种是直接使用 Servlet 原生 API。Spring MVC 的 Controller 方法参数中可以直接注入 HttpServletResponse,然后通过 javax.servlet.http.Cookie(新版本是 jakarta.servlet.http.Cookie)构造对象,再调用 addCookie 写入响应。这种方式最直观,兼容性也最好。
@GetMapping("/login")
public String login(HttpServletResponse response) {
Cookie cookie = new Cookie("token", "abc123");
cookie.setHttpOnly(true); // 禁止 JS 读取,防 XSS 窃取
cookie.setSecure(true); // 仅 HTTPS 下发送
cookie.setPath("/"); // 生效路径
cookie.setMaxAge(7 * 24 * 60 * 60); // 存活 7 天,单位秒
response.addCookie(cookie);
return "ok";
}需要注意的是,setMaxAge 的取值有三种含义:正数表示存活指定秒数;负数表示会话级 Cookie,浏览器关闭即失效(默认值就是 -1);0 表示立即删除该 Cookie,这是删除 Cookie 的标准做法。
第二种方式是使用 Spring 5 引入的 ResponseCookie。它采用链式构造器的写法,支持直接设置 sameSite 属性,这是原生 Cookie 类做不到的。如果你的项目前后端分离、需要精细控制 Cookie 属性,推荐用这种写法。
@GetMapping("/login2")
public String login2(HttpServletResponse response) {
ResponseCookie cookie = ResponseCookie.from("token", "abc123")
.httpOnly(true)
.secure(true)
.path("/")
.sameSite("Lax") // 可直接指定 Lax / Strict / None
.maxAge(Duration.ofDays(7))
.build();
response.addHeader("Set-Cookie", cookie.toString());
return "ok";
}两种方式的本质都是往响应头里写 Set-Cookie 字段,区别只在于 API 的封装层次。ResponseCookie 会自动处理属性拼接和编码问题,出错概率更低。
二、在 Controller 中读取 Cookie
读取 Cookie 最常用的方式是借助 @CookieValue 注解,直接把某个 Cookie 的值绑定到方法参数上,代码非常简洁。
@GetMapping("/user")
public String getUser(@CookieValue("token") String token) {
// 直接拿到名为 token 的 Cookie 值
return "当前 token:" + token;
}如果 Cookie 可能不存在,@CookieValue 会直接抛出异常,所以更稳妥的写法是加上 required = false 并给默认值:
@GetMapping("/user")
public String getUser(@CookieValue(value = "token", required = false, defaultValue = "") String token) {
if (token.isEmpty()) {
return "未登录";
}
return "当前 token:" + token;
}当需要遍历所有 Cookie 时,可以注入 HttpServletRequest,调用 getCookies() 拿到数组后逐个筛选。这种方式适合写拦截器或过滤器,比如统一提取登录态。
@GetMapping("/cookies")
public String list(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies == null) {
return "没有任何 Cookie";
}
for (Cookie c : cookies) {
System.out.println(c.getName() + " = " + c.getValue());
}
return "已打印到控制台";
}有一点要提醒:Cookie 是随请求自动携带的,浏览器只会把满足 Domain 和 Path 匹配条件的 Cookie 发给服务端。如果发现 Cookie 读不到,先用浏览器开发者工具的 Network 面板确认请求头里有没有 Cookie 字段,再检查设置时的 Path 是否写成了过窄的路径。
三、SameSite 属性详解与全局配置
SameSite 是用来防御 CSRF 攻击的 Cookie 属性,它控制浏览器在跨站请求时是否携带 Cookie,共有三个取值。Strict 最严格,任何跨站请求都不带 Cookie,即使用户从别的网站点链接跳转过来也不会带,安全性最高但体验有损。Lax 是目前的浏览器默认值,顶级导航的 GET 请求(比如点击链接)会带 Cookie,但表单提交、iframe、Ajax 跨站请求都不带,安全和体验比较均衡。None 则完全不做限制,跨站请求照样携带,但必须同时设置 Secure=true,也就是只能在 HTTPS 环境下生效,否则浏览器会直接拒绝这个 Cookie。
典型的踩坑场景:前后端分离部署在不同域名下,前端用 withCredentials: true 发跨域请求,但后端设置的 Cookie 没有声明 SameSite,浏览器按 Lax 处理,跨站请求根本不带 Cookie,登录态自然丢失。解决思路有两个层面。
第一种是针对单个 Cookie,用前面提到的 ResponseCookie 直接指定 sameSite("None") 并开启 secure,精确控制。
第二种是全局配置。Spring Boot 内嵌 Tomcat 从 2.6 版本开始支持通过配置文件直接声明默认的 SameSite 策略,在 application.properties 中添加:
server.servlet.session.cookie.same-site=Lax server.servlet.session.cookie.secure=true server.servlet.session.cookie.http-only=true
这个配置只对 Spring Session 生成的会话 Cookie 生效。如果想影响所有经过 Tomcat 写出的 Cookie,可以自定义 CookieProcessor:
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> cookieCustomizer() {
return factory -> {
TomcatContextCustomizer customizer = context -> {
LegacyCookieProcessor processor = new LegacyCookieProcessor();
processor.setSameSiteCookies("Lax");
context.setCookieProcessor(processor);
};
factory.addContextCustomizers(customizer);
};
}如果是 Filter 层面写入的认证 Cookie,还可以让过滤器继承 OncePerRequestFilter 并配合 CookieSerializer(Spring Session 提供)来统一控制序列化行为,这在多实例部署需要共享会话的架构里更常见。
最后强调一个硬性规则:SameSite=None 必须搭配 Secure 属性,且页面必须通过 HTTPS 访问。本地开发时如果用 http://localhost 调试,Chrome 对 localhost 有豁免,但 Firefox 不一定买账,最稳妥的办法还是给本地也配上自签证书,或者临时降级为 Lax 观察。掌握这些配置点后,无论是登录态维护还是跨域资源共享场景,Cookie 相关的问题都能快速定位和解决。
Spring BootCookieSameSite修改时间:2026-09-15 03:44:30