面试官问:Cookie 和 localStorage 有什么区别?为什么 Cookie 能 "自动带上",localStorage 不行?SameSite=Strict 和 Lax 到底差在哪?很多人能背出 HttpOnly、Secure 几个属性名,但一被问到 Cookie 是怎么产生的、浏览器凭什么决定带不带、跨域为什么就失效,就答不上来了。
这篇文章从 HTTP 无状态讲起,把 Cookie 的产生、属性、发送规则、安全攻防一次讲透。文末附面试高频题。
1. 从 HTTP 无状态说起:Cookie 为什么存在
HTTP 协议是无状态的:每一次请求都是独立的,服务器不会 "记得" 你。
这带来一个直接问题:早期的电商网站没法实现 "购物车"。用户往购物车加了商品,再点下一步,服务器已经不认识他了。
Cookie 就是为解决这个问题诞生的:服务器在响应里塞一张 "小纸条"(键值对),浏览器保存下来,下次请求时自动把小纸条原样带回。服务器一看纸条,就知道 "哦,还是刚才那个人"。
一句话:Cookie 是 HTTP 从 "无状态" 走向 "有状态" 的补丁。它本身不解决 "你是谁",只是提供了一种 "浏览器帮我记住、自动回传" 的机制。
2. Cookie 的工作原理
2.1 服务器下发:Set-Cookie 响应头
服务器想给浏览器 "发一张纸条",就在响应里加一个 Set-Cookie 响应头:
ini
HTTP/1.1 200 OK
Content-Type: text/html
Set-Cookie: session_id=abc123; Path=/; HttpOnly
浏览器收到后,把 session_id=abc123 连同属性一起存进本地 Cookie 仓库。
服务端代码(Node.js/ Express):
php
res.cookie('session_id', 'abc123', {
path: '/',
httpOnly: true,
maxAge: 7 * 24 * 3600 * 1000, // 7 天
sameSite: 'lax'
});
Python / Flask:
python
from flask import make_response
resp = make_response('ok')
resp.set_cookie('session_id', 'abc123', path='/', httponly=True, max_age=604800, samesite='Lax')
return resp
2.2 浏览器回传:Cookie 请求头
之后浏览器再向同一站点发起请求时,会从本地 Cookie 仓库里找出匹配的 Cookie,拼成 Cookie 请求头自动带上:
vbnet
GET /cart HTTP/1.1
Host: shop.example.com
Cookie: session_id=abc123
服务器读取请求头里的 Cookie,就能识别出会话。整个过程浏览器自动完成,JS 和用户都不需要干预。
2.3 完整生命周期
3. Cookie 的关键属性
一个完整的 Set-Cookie 长这样:
ini
Set-Cookie: user=zhang; Domain=example.com; Path=/api; Max-Age=3600; Secure; HttpOnly; SameSite=Lax; Priority=High
| 属性 | 作用 | 示例 | 常见坑 |
|---|---|---|---|
| Name=Value | Cookie 本身的内容 | user=zhang | 值要 encodeURIComponent,不能有分号逗号空格 |
| Domain | 发给哪些域名 | Domain=example.com | 写错域名 Cookie 直接失效;不写则只发给设置它的主机 |
| Path | 发给哪些路径 | Path=/api | 默认取请求路径的目录;/ 表示全站 |
| Expires | 过期时间(绝对时间) | Expires=Wed, 21 Oct 2026 07:28:00 GMT | 会话级 Cookie 不写它,关浏览器就删 |
| Max-Age | 存活秒数(相对时间) | Max-Age=3600 | 与 Expires 同时存在时 Max-Age 优先 |
| Secure | 仅 HTTPS 发送 | Secure | 本地 HTTP 环境(除 localhost)会失效 |
| HttpOnly | JS 无法读取 | HttpOnly | 防 XSS 偷 Cookie 的关键,但不影响自动发送 |
| SameSite | 跨站请求是否携带 | SameSite=Strict/Lax/None | None 必须搭配 Secure;默认值各浏览器不同(Chrome 默认 Lax) |
| Priority | 淘汰优先级 | Priority=High/Low | 浏览器 Cookie 超额时先淘汰 Low |
| Partitioned | 第三方 Cookie 分区存储 | Partitioned | 较新(CHIPS),用于替代被禁的第三方 Cookie |
按功能可以把属性分成三组:
几个容易记混的点单独说一下:
Expires vs Max-Age :Expires 是 "绝对到期时间点",Max-Age 是 "还能活多少秒"。两者同时出现时,Max-Age 优先(RFC 6265 规定)。
Domain 的两种模式:
-
不写 Domain → host-only Cookie :只发给设置它的那个主机(比如只发 api.example.com),子域收不到
-
写 Domain=example.com → 发给 example.com 及其所有子域
注意:Domain 不能设为公共后缀(如 .com、.cn),浏览器会拒绝。
Path 默认值 :如果不写 Path,浏览器默认取 "发起该请求的 URL 的目录部分"。比如请求 a.com/user/profil... 时设置的 Cookie,默认 Path=/user。想全站生效就显式写 Path=/。
-
__Host- / __Secure- 前缀:给 Cookie 名字加前缀可以 "强制" 安全属性:
-
__Secure-name:必须带 Secure 且走 HTTPS
-
__Host-name:必须带 Secure、Path=/、且不能有 Domain(即必须是 host-only)
这是浏览器层面的硬校验,能防止某些 "Cookie 注入 / 前缀混淆" 攻击,适合放登录凭证。
4. Cookie 的创建、修改与删除
4.1 三种创建方式
方式一:服务端响应头(最常用)
ini
Set-Cookie: theme=dark; Path=/; Max-Age=2592000
方式二:JS 的 document.cookie
javascript
// 设置(不会覆盖其他 Cookie,同名同路径才覆盖)
document.cookie = 'theme=dark; Path=/; Max-Age=2592000';
// 读取(返回所有可读 Cookie 拼接的字符串)
console.log(document.cookie); // "theme=dark; user=zhang"
注意:document.cookie 读不到 HttpOnly 的 Cookie,这是设计使然。
方式三:Cookie Store API(较新)
csharp
await cookieStore.set({ name: 'theme', value: 'dark', path: '/' });
const cookies = await cookieStore.getAll();
兼容性有限(Chrome/Edge 支持较好),生产环境用前先查兼容性。
4.2 修改与删除
- 修改 :重新 Set 一条同名、同 Domain、同 Path 的 Cookie,值会被覆盖;任一项不同则视为新 Cookie(会出现两条)。
- 删除:设置 Max-Age=0(或 Expires 设为过去时间),同名同 Domain 同 Path 覆盖即可删除。
ini
// 删除
document.cookie = 'theme=; Path=/; Max-Age=0';
ini
// 服务端删除
Set-Cookie: theme=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT
最容易踩的坑:删除时 Path / Domain 必须和创建时完全一致,否则删不掉(会悄悄新建一条空值 Cookie)。
5. 浏览器如何决定 "带不带这个 Cookie"
很多人以为 "Cookie 是同源的,跨域就不带",其实不完全对。浏览器判断是否携带某个 Cookie,是按下面这套规则逐条校验的:
5.1 Domain 匹配
- host-only Cookie:请求主机名必须完全等于设置主机名
- 带 Domain 的 Cookie:请求主机名必须等于 Domain 值或它的子域
5.2 Path 匹配
请求路径必须以 Path 值为前缀。Path=/api 只匹配 /api/...,不匹配 /apix(按 / 边界匹配)。
5.3 协议与过期
- Secure Cookie 只在 HTTPS(或 localhost 等安全上下文)下发送
- 已过期的 Cookie 不发送,同时浏览器会把它清理掉
5.4 同站判定(SameSite 的底层逻辑)
这里要引入一个容易混淆的概念:
同源(Same-Origin)≠ 同站(Same-Site)
同源:协议 + 域名 + 端口三者完全一致
同站:只看 "站"(eTLD+1,即注册域名),协议不同、端口不同
不算跨站
例如 shop.com 和 shop.com:协议不同(跨源),但同站 。a.com 和 b.com 才是跨站。
SameSite 管的就是 "跨站请求带不带 Cookie":
- Strict:跨站请求一律不带(包括用户点了站外链接跳转过来)
- Lax:跨站请求中,只有顶级导航的 GET 请求 带(点链接跳转、地址栏输入会带),图片 /iframe/fetch 等子资源请求不带 ------ Chrome 80+ 的默认值
- None:跨站请求都带,但必须同时满足 Secure(仅 HTTPS)
5.5 第一方 vs 第三方 Cookie
- 第一方 Cookie:由你正在访问的站点(地址栏里的站点)设置的 Cookie
- 第三方 Cookie :页面上嵌入的其他站点资源(广告 iframe、第三方脚本、CDN)设置的 Cookie,它们的 "站" 和地址栏里的站不同
第三方 Cookie 是广告追踪和跨站登录(SSO)的基础,也是近年浏览器重点打击的对象(见第 7 节)。
6. Cookie 与 Session、Token 的关系
一个最常见的误解:把 Cookie 当成认证方案本身。
实际上:
Cookie 只是 "运输工具",Session / Token 才是 "凭证内容"。
认证方案可以坐在 Cookie 这辆车里,也可以自己走别的路(比如 Authorization 请求头)。
| 场景 | 凭证放哪 | 说明 |
|---|---|---|
| Session 时代 | sessionId 放 Cookie | 服务端查表验证 |
| Token 时代 | accessToken 放请求头、refreshToken 放 httpOnly Cookie | 双 Token 机制(见《Token 与 RefreshToken 一次讲透》) |
| 无 Cookie 方案 | 请求头 / 移动端安全存储 | App、跨端场景 |
所以 Cookie 的典型价值是:浏览器自动携带 ,前端不用手动管理 "把凭证塞进每个请求";代价是自动携带也带来了 CSRF 风险(见第 8 节)。
6.1 和浏览器其他存储对比
| 维度 | Cookie | localStorage | sessionStorage | IndexedDB |
|---|---|---|---|---|
| 大小 | 单条约 4KB,每域几十条 | 约 5MB | 约 5MB | 大(GB 级) |
| 自动随请求发送 | 是 | 否 | 否 | 否 |
| 过期机制 | Expires/Max-Age | 手动清除 | 关标签页清除 | 手动清除 |
| JS 可读 | 受 HttpOnly 限制 | 可 | 可 | 可 |
| 作用域 | Domain/Path 控制 | 同源 | 同源(按标签页) | 同源 |
| 典型用途 | 会话凭证、偏好 | 前端缓存、主题 | 一次性页面状态 | 复杂离线数据 |
选型建议 :需要 "服务器每次都能自动收到" 的数据(会话凭证、A/B 实验 ID)用 Cookie;纯前端本地数据用 localStorage;敏感凭证不要放 localStorage(XSS 可读,详见《Token 与 RefreshToken 一次讲透》)。
7. 第三方 Cookie 与跨站(SameSite 深水区)
7.1 第三方 Cookie 是怎么产生的
你在浏览 news.com,页面里嵌了一个广告 iframe(ad.com)。这个 iframe 里的脚本请求 ad.com 的接口,ad.com 在响应里种下 Cookie:
于是 ad.com 能在所有嵌了它广告的网站上识别同一个用户,实现跨站追踪。
7.2 为什么浏览器要禁它
第三方 Cookie 是跨站追踪的基础设施,严重冲击隐私。近几年的浏览器政策:
- Safari:ITP(智能防追踪)默认拦截第三方 Cookie
- Firefox:默认拦截第三方 Cookie
- Chrome:逐步推进禁用第三方 Cookie(2024 年起分阶段灰度),并推出替代方案
7.3 开发者该怎么办
- 需要跨站携带的 Cookie:设置 SameSite=None; Secure(但会被浏览器逐步拦截)
- 新的替代方案:CHIPS(Partitioned Cookie) ------ 给 Cookie 加 Partitioned 属性,让它在 "每个顶级站点" 下独立存储,既能跨站工作又不被全局追踪
- 长期方向:把第三方 Cookie 的追踪能力迁移到第一方数据(自己的域名、服务端会话、邮箱登录等)
ini
// CHIPS 示例:第三方 Cookie 分区存储
Set-Cookie: ad_click=1; SameSite=None; Secure; Partitioned
8. 安全:Cookie 常见攻击与防护
8.1 攻击面总览
8.2 逐个攻防
| 威胁 | 原理 | 防护 |
|---|---|---|
| XSS 窃取 Cookie | 恶意脚本执行 document.cookie 读走凭证 | 凭证 Cookie 加 HttpOnly;配合 CSP、输入输出转义 |
| CSRF(跨站请求伪造) | 浏览器会自动携带 Cookie,恶意站点诱导用户发请求 | SameSite=Lax/Strict;校验 Origin/Referer;CSRF Token |
| 中间人窃听 | 明文 HTTP 下 Cookie 被抓包 | Secure 属性 + 部署 HSTS 强制 HTTPS |
| Cookie 投毒 / 篡改 | Cookie 里直接存用户名、角色等明文,攻击者改内容 | Cookie 只存不透明标识符(如 sessionId),服务端查表验证;重要数据签名 |
| 会话固定攻击 | 攻击者先种一个 sessionId,诱导用户用它的 ID 登录 | 登录成功后重新生成 sessionId(轮换) |
| 子域 Cookie 注入 | 任意子域可给父域写 Cookie(若 Domain 设了父域) | 敏感 Cookie 用 __Host- 前缀(强制 host-only + Secure + Path=/) |
几个要点展开:
HttpOnly 不是万能的 :它只挡 JS 读取,不挡自动发送,也挡不住 XSS 通过其他方式(比如表单提交、图片加载)把请求发出去 ------ 所以 XSS 本身还是要治(CSP、转义、第三方脚本管控)。
CSRF 的本质:攻击者不发你的 Cookie,但浏览器会自动带。防御核心是让 "跨站请求" 和 "合法请求" 长得不一样:SameSite 让浏览器跨站不带,Origin/Referer 校验让服务端能区分,CSRF Token 让伪造请求没有合法凭据。
HSTS:Strict-Transport-Security 响应头告诉浏览器 "这个域名以后只能走 HTTPS",防止中间人先把 HTTPS 降级成 HTTP 再抓 Cookie。
9. Cookie 的存储限制与性能
9.1 数量与大小
RFC 6265 要求浏览器至少支持:
- 每个 Cookie ≤ 4096 字节(约 4KB)
- 每个域 ≥ 50 个 Cookie
- 总数 ≥ 3000 个
主流浏览器实际按此基线执行(Chrome 每域约 180 个、Firefox 约 150 个,随版本变化),超出后按 "最不活跃的先淘汰"(Priority 属性可干预)。
9.2 性能影响
每个请求都会自动带上匹配的 Cookie。Cookie 体积越大、条数越多,每个请求的头部就越臃肿:
- 首屏静态资源(图片、JS、CSS)也会带 Cookie → 浪费带宽
- 解决:静态资源放独立 CDN 域名 / 子域 (比如 static.example.com 不设置 Domain 到 example.com),避免携带会话 Cookie
别把大对象放 Cookie:用户偏好、购物车这种低频大对象,优先放 localStorage / 服务端;Cookie 里只放 "每次请求都必须让服务器知道" 的最小凭证。
10. 常见的坑
- localhost 下 Secure Cookie 失效 :本地 HTTP 调试时带 Secure 的 Cookie 不会发送(Chrome 对 localhost 视为安全上下文,可用 HTTPS 或去掉 Secure 调试)。
- Domain 写错导致 "登录态丢失" :在 api.example.com 设置了不带 Domain 的 Cookie,前端页面在 www.example.com 收不到。
- 删除 Cookie 条件不一致:删除时 Path/Domain 与创建时不一致 → 删不掉。
- SameSite 默认值差异:老项目假设默认是 None,Chrome 80 后默认 Lax,跨站 iframe 里的登录态突然失效。
- SameSite=None 忘了加 Secure:浏览器直接拒绝该 Cookie。
- Cookie 值没编码:值里带分号、逗号、空格会导致解析错乱,需要 encodeURIComponent。
- 静态资源背 Cookie:CDN 资源没和 API 域名分离,每个静态请求都背一堆 Cookie,拖慢首屏。
- HttpOnly Cookie 无法用 JS 删除:需要服务端在响应里 Set-Cookie 过期(和上一篇 "登出只清前端" 是同一个坑)。
- 把敏感数据明文放 Cookie:用户能直接改,服务端必须验签或查表。
- 移动端 App 没有 Cookie 机制:原生请求不会自动带 Cookie,需要自己管理凭证(Keychain/Keystore)。
11. 面试高频 Q&A
Q1:Cookie 和 localStorage 有什么区别?
最核心的差异是 "会不会自动随请求发送":Cookie 会(这也是会话凭证选它的原因),localStorage 不会。其次是容量(Cookie 约 4KB / 条,localStorage 约 5MB)、过期机制(Cookie 有 Expires/Max-Age,localStorage 只能手动清)和作用域(Cookie 可以按 Domain/Path 精细控制,localStorage 严格同源)。
Q2:HttpOnly 和 Secure 有什么区别?
HttpOnly 管的是 "JS 能不能读 "(防 XSS 窃取),Secure 管的是 "什么协议下才发送"(防明文窃听,仅 HTTPS)。两者是互补关系,敏感凭证 Cookie 一般都要加。
Q3:SameSite=Strict 和 Lax 的区别?为什么默认不是 Strict?
Strict 跨站一律不带,Lax 允许 "顶级导航的 GET"(点站外链接跳转)带。不用 Strict 做默认是因为体验:用户从邮件 / 外部链接点进你的站,Strict 下 Cookie 不发送,登录态和偏好直接 "消失",体验像没登录一样;Lax 在保证基本防 CSRF 的同时保住这个场景。
Q4:为什么 Cookie 能自动带上,而 token 要手动加?
Cookie 是浏览器原生机制:匹配 Domain/Path/ 过期规则后,浏览器自动把 Cookie 拼进请求头。token 放在请求头需要前端代码主动注入(比如 axios 拦截器)。Cookie 省事但有 CSRF 风险,所以现在更推荐 "token 放请求头 + refreshToken 放 httpOnly Cookie" 的组合。
Q5:第三方 Cookie 是什么?为什么被禁用?
页面里嵌入的其他站点资源(广告 iframe、第三方脚本)设置的 Cookie 就是第三方 Cookie。它能被广告商用来跨站追踪用户,侵犯隐私,所以 Safari/Firefox/Chrome 逐步默认拦截。替代方案包括 CHIPS(Partitioned)和第一方数据策略。
Q6:Cookie 能跨域吗?
"跨域带 Cookie" 和 "跨域设 Cookie" 要分开看:SameSite=None; Secure 可以让 Cookie 在跨站请求中携带(第三方 Cookie);设置 Cookie 本身由 Domain 控制(不能设到别的域)。而 withCredentials/credentials 是前端跨域请求时是否带上 Cookie 的开关(需要服务端配合 CORS 允许)。
Q7:登录状态存 Cookie 安全吗?
安全的关键不在 "存哪",而在 "存什么 + 怎么保护":只存不透明标识符(sessionId,服务端查表),配合 HttpOnly + Secure + SameSite 三件套,登录后轮换 ID,就是主流且安全的方案;直接存明文用户信息或可被 JS 读取的长期凭证才是危险的。
12. 总结
一句话:
Cookie 是服务器下发给浏览器、由浏览器自动回传的 "身份凭证小纸条",是 HTTP 无状态协议走向 "有状态" 的关键补丁。
- 工作原理:Set-Cookie 下发 → 浏览器存储 → 匹配 Domain/Path/ 过期 /SameSite 规则 → 自动携带 → 服务端识别
- 关键属性:Domain/Path 管 "发给谁",Expires/Max-Age 管 "活多久",HttpOnly/Secure/SameSite 管 "安不安全"
- 安全三件套:HttpOnly + Secure + SameSite,再加登录后轮换 sessionId、服务端验签
- Cookie 是 "运输工具",Session / Token 是 "凭证内容",不要混淆
- 限制与性能:单条约 4KB、每域几十条,静态资源要和 API 域名分离,避免无谓的 Cookie 开销
Cookie 是前端面试和日常开发都绕不开的基础设施。希望这篇能帮你把它的原理、属性、发送规则和安全边界一次理清。有问题评论区见。