~/ ?.log $
返回文章列表
11 min read
更新于 2026年3月6日

我不知道的HTTP(02)— Cookie到底是怎么工作的

很多人以为 Cookie 就是"在浏览器里存个值",需要的时候取出来用。但实际上,Cookie 和 localStorage 有一个本质区别:Cookie 会在每一次 HTTP 请求中被自动携带发送到服务器。

一、问题引入

很多人以为 Cookie 就是”在浏览器里存个值”,需要的时候取出来用。但实际上,Cookie 和 localStorage 有一个本质区别:Cookie 会在每一次 HTTP 请求中被自动携带发送到服务器

这意味着什么?每多设置一个 Cookie,每个请求的体积就会增大。一个域名下如果积累了 4KB 的 Cookie,那这个域名下的每一个请求——包括每张图片、每个 CSS 文件——都会多带上 4KB 的请求头。Cookie 不仅仅是存储机制,它更是一个请求级别的状态传输通道。

二、表面认知:大多数人停在这里

大多数开发者对 Cookie 的理解停留在这个层面:服务端在响应头里设置 Set-Cookie,浏览器自动保存下来,后续请求自动通过 Cookie 头带回去。

HTTP/1.1 200 OK
Set-Cookie: session_id=abc123; Path=/; HttpOnly

后续同域请求会自动携带:

GET /api/user HTTP/1.1
Cookie: session_id=abc123

这个基本模型没错。但”自动”两个字背后,有一套复杂的规则在决定:哪些请求会带、哪些不会带、带给谁、不带给谁。理解这套规则,才算真正搞懂 Cookie。

三、往下挖:Cookie 的规则引擎

3.1 Domain 属性:Cookie 到底发给谁

设置 Cookie 时,Domain 属性决定了哪些域名可以收到这个 Cookie。

这里有一个很多人会忽略的细节——如果不显式设置 Domain,Cookie 只会精确匹配当前域名,不包含子域名。但如果显式设置了 Domain=example.com,那 a.example.comb.example.com 甚至 x.y.example.com 都能收到这个 Cookie。

换句话说,“不设置”和”设置为当前域”的行为是不同的。不设置更严格,设置了反而更宽泛。

这在多子域架构中尤其重要。假设 login.example.com 负责登录,设置了 Domain=example.com 的 session Cookie,那 api.example.comadmin.example.com 都能读到这个 Cookie。这是单点登录的常见实现方式,但同时也意味着——任何子域名被攻破,都能拿到这个 Cookie。一个被遗忘的测试子域 test.example.com 如果存在 XSS 漏洞,就能直接窃取主站的 session。

SameSite 是 Cookie 最重要的安全属性之一,也是最容易搞混的。它控制的是:当请求是从其他站点发起的(比如从 evil.combank.com 发请求),Cookie 要不要跟着一起发。

问题的关键在于——SameSite 有三个值,每个值在不同场景下的行为完全不同。用列表罗列很难记住,用场景更直观。

场景一:用户在 evil.com 上点击了一个链接,跳转到 bank.com

这是一个顶级导航(top-level navigation),属于”安全”的跨站请求。SameSite=Lax 会携带 Cookie,SameSite=Strict 不会。Strict 的意思是:除非请求是从 bank.com 自己发起的,否则一律不带。所以如果把 session Cookie 设成 Strict,用户从搜索引擎点击链接进来,就会发现自己是未登录状态——因为 Cookie 没有跟过来。

场景二:evil.com 页面中嵌了一个隐藏表单,自动向 bank.com/transfer 发 POST 请求。

这是经典的 CSRF 攻击。POST 请求不是顶级导航,SameSite=LaxSameSite=Strict 都不会携带 Cookie。攻击失败。这就是为什么 Chrome 在 2020 年把 SameSite 的默认值从 None 改成了 Lax——仅这一个默认值的修改,就让大量 CSRF 攻击自动失效了

场景三:a.com 页面中用 <iframe> 嵌入了 b.com,或者用 fetch 跨站请求 b.com 的 API。

这两种情况下,只有 SameSite=None 的 Cookie 才会被发送——而且必须同时设置 Secure 标记(只能通过 HTTPS 传输)。这就是第三方 Cookie 的工作方式。

下面这段代码演示了三种 SameSite 值的设置方式,可以在 Express 服务中直接运行:

const express = require('express');
const app = express();

app.get('/set-cookies', (req, res) => {
  res.setHeader('Set-Cookie', [
    'strict_cookie=1; SameSite=Strict; Path=/',
    'lax_cookie=1; SameSite=Lax; Path=/',
    'none_cookie=1; SameSite=None; Secure; Path=/',
  ]);
  res.send('Cookies set');
});

app.listen(3000);

设置后,从其他站点发起不同类型的请求,在 DevTools 的 Network 面板中观察 Cookie 头,就能清楚看到三者的差异:跨站链接跳转只带 lax_cookie,跨站 POST 一个都不带,跨站 iframe/fetch 只带 none_cookie

3.3 HttpOnly 和 Secure:两道经常被忽视的防线

HttpOnly 禁止 JavaScript 通过 document.cookie 读取该 Cookie。说白了,HttpOnly 就是专门防 XSS 的——即使攻击者在页面中注入了恶意脚本,也无法通过 JS 把 session Cookie 偷走。

Secure 标记要求 Cookie 只能通过 HTTPS 传输。如果用户不小心通过 HTTP 访问了页面,Secure Cookie 不会被发出去,避免在明文传输中被截获。

如果你只记住一句话,记住这个:session 类 Cookie 应该同时设置 HttpOnlySecureSameSite=Lax(或 Strict),三者缺一不可。

3.4 Expires 和 Max-Age:Cookie 什么时候消失

不设置过期时间的 Cookie 是会话级的——关闭浏览器(注意是关闭整个浏览器,不是关闭标签页)就会被清除。设置了 Expires(绝对时间)或 Max-Age(相对秒数)的 Cookie 是持久化的,到期自动删除。

这里有一个很多人会忽略的细节——当 ExpiresMax-Age 同时存在时,Max-Age 优先。而 IE 8 及更早版本不支持 Max-Age,只认 Expires。现在已经不需要为 IE 8 操心了,推荐统一使用 Max-Age,因为它不依赖客户端的时钟是否准确。

3.5 大小限制与性能影响

每个 Cookie 的大小上限通常是 4KB,单个域名下最多约 50 个 Cookie(不同浏览器略有差异)。

说白了,Cookie 的性能问题不在于”读写慢”,而在于”每次请求都带”。如果一个域名下有 3KB 的 Cookie,该域名下的 100 个资源请求就会多传输 300KB 的请求头数据。这也是为什么很多大型网站会把静态资源(图片、CSS、JS)放到独立的域名(比如 static.example.com)——那个域名下没有 Cookie,请求头干干净净。

关于第三方 Cookie 的未来,这几年的变化非常大,很多文章还停留在旧信息上。

(1) Google 在 2020 年宣布计划淘汰 Chrome 中的第三方 Cookie,并推出 Privacy Sandbox 作为替代方案。其中最知名的提案 FLoC(Federated Learning of Cohorts)因为隐私争议在 2022 年被废弃,替代方案 Topics API 上线后也未获得广泛采用。

(2) 2025 年 4 月,Google 正式宣布放弃在 Chrome 中淘汰第三方 Cookie 的计划。2025 年 10 月,Privacy Sandbox 项目正式关闭。Chrome 继续保留第三方 Cookie。

(3) 但 Safari 和 Firefox 没有放弃。Safari 的 ITP(Intelligent Tracking Prevention)从 2017 年开始就在持续收紧第三方 Cookie 的限制,目前默认完全阻止第三方 Cookie。Firefox 的 TCP(Total Cookie Protection)为每个站点创建独立的 Cookie 存储,使第三方 Cookie 无法跨站跟踪。

换句话说,第三方 Cookie 和”被淘汰”的根本区别在于:它没有被统一淘汰,而是进入了一个”浏览器各自为政”的分裂状态。Chrome 保留,Safari/Firefox 限制,开发者需要面对的是差异化行为。

值得关注的新方向包括:CHIPS(Cookies Having Independent Partitioned State)——允许第三方 Cookie 存在,但按顶级站点分区,使其无法跨站跟踪;以及 DBSC(Device Bound Session Credentials)——将 session 绑定到设备的硬件密钥上,即使 Cookie 被窃取也无法在其他设备上使用。

五、总结

Cookie 不是一个存储 API,而是一个请求级的状态传输协议。它的每一个属性——Domain、SameSite、HttpOnly、Secure——都在回答同一个问题:这个值该发给谁、在什么条件下发、怎么防止被偷走。


本系列其他文章:

相关主题:

share.ts

// 觉得这篇文章有帮助?

// 欢迎分享给更多人

export const  subscribe  =  "/rss.xml" ;