一个后台系统在自家域名下登录正常,被嵌到客户的 iframe 里后"登录成功但立刻掉线"。原因是 Cookie 设了 SameSite=Lax,而嵌入场景下的请求是"跨站"的,于是浏览器不带 Cookie——服务端看到"没登录",返回 401,前端跳回登录页。
这类问题的麻烦在于"它生效了但只在某些场景失效":同站访问正常、跨站/嵌入/第三方跳转全挂。这个"没有提示"的性质很重要:浏览器不会在响应里说"我拒了你的 Cookie",服务端只看到"请求没带"。于是"是前端没设"还是"浏览器拒收",需要靠 DevTools 才能区分。
而排查时你看到的日志是"没有 Cookie",而不是"Cookie 被拒"——浏览器不告诉你原因。
1. 三种 SameSite 值
| 值 | 行为 |
|---|---|
Strict |
任何跨站请求都不带 |
Lax |
顶层导航的 GET 带,其它不带 |
None |
都带(必须配 Secure) |
第二项是默认值(Chrome 80+ 起):它保证了"点链接进来"能带上 Cookie,但"嵌入/表单提交/AJAX"不带。第二项的"顶层导航"有个细节:它区分"GET 顶层导航"与"其它请求"——所以"从外部链接点进来"通常能带 Cookie,而"页面内 AJAX 到别的域"不能。
第三项的"唯一"正在变弱:第三方 Cookie 的整体收紧让 SameSite=None 也不再总有效。所以"依赖第三方 Cookie"的架构本身在退潮。
第三项是"跨站必须可用"时的唯一选择,而它有两个硬约束:必须 Secure(HTTPS),且第三方 Cookie 还受浏览器策略限制(Safari 默认阻止,Chrome 逐步收紧)。
2. "同站"的判定不是"同域"
| 例子 | 是否同站 |
|---|---|
a.example.com ↔ b.example.com |
同站(同注册域) |
a.example.com ↔ a.other.com |
跨站 |
example.com:8080 ↔ example.com:9090 |
同站(端口不影响) |
http://x ↔ https://x |
同站(协议不影响 SameSite 判定) |
第二项是"跨站"的常见误判:域名后缀不同就是跨站,即使它们属于同一家公司。第二项还有个"子域"细节:a.example.com 与 example.com 是同站(同注册域),所以"子域之间共享 Cookie"在 SameSite 上是成立的——前提是 Domain 设对。
第四项常被误解:SameSite 只看"注册域",与协议无关;但 Secure 属性只看协议。
3. 五种"登录失效"的场景
| 场景 | 原因 |
|---|---|
| iframe 嵌入 | 第三方上下文,Lax 不带 |
| 跨站表单 POST | 非顶层 GET,Lax 不带 |
| 支付回调跳回 | 跨站顶层 GET(Lax 通常可带) |
| 移动端 WebView | Cookie 策略与浏览器不同 |
| 多域名部署 | 同站判定不同 |
第一项(iframe)还有一个"双重"问题:嵌入方与被嵌入方都要允许(X-Frame-Options/CSP frame-ancestors),否则页面根本加载不出来——而"加载不出来"与"登录掉线"是两种症状。
第一项与第五项最常被误认为"后端 bug":服务端确实没收到 Cookie,但它不是"没发",而是"浏览器没带"。
"本地开发"还有一处必须显式配置,否则"本地好后端就坏了":
// 本地与生产的三个差异(容易忽略)
1 协议:本地 http,生产 https → Secure 属性在本地被拒
2 域名:localhost 与 example.com → Domain 属性不能照抄
3 端口:8080 与 80 → SameSite 不看端口,但 CORS 看 origin
// 建议:用配置文件区分,而不是"本地注释掉 Secure"
4. 四种应对
| 方案 | 代价 |
|---|---|
改 SameSite=None; Secure |
需 HTTPS + 第三方 Cookie 策略允许 |
| 用 token(Header 传递) | 改认证方式 |
| 同站代理(把嵌入页代理到同域) | 架构改动 |
| 分区 Cookie(CHIPS) | 兼容性有限 |
第二项是"最彻底"的:把认证凭据从 Cookie 换成 Header 里的 token第二项的代价有个具体项:"跨页持久化"——Cookie 是浏览器自动带的,而 Header token 需要前端存(localStorage/内存)并在每个请求加。于是"存哪"变成了新的安全问题(XSS 可读 localStorage)。
,于是"带不带 Cookie"不再影响认证。代价是"存储位置"与"XSS 风险"要重新考虑。
第三项的实现要点是"路径重写要彻底":页面里的绝对路径(/api/...、/static/...)都要被代理,否则"部分资源跨站加载"又会触发同样的 Cookie 问题。
第三项在"必须嵌入"的场景里实用:用同域的反向代理把第三方页面"本地化",于是同站判定成立。代价是多一层代理与路径重写。
// 三种方案的选择判据
能不能用 HTTPS + 第三方 Cookie?→ 是:SameSite=None; Secure(最省)
认证方式能否改 Header token? → 能:换 token(最稳)
都做不到? → 同站代理(改动最大)
四个属性要一起看,任何一个不对都会"设了但不生效":
Set-Cookie: sid=abc123; Path=/; Domain=.example.com; Secure; HttpOnly; SameSite=None
// Path=/ 路径不匹配 → 该路径拿不到
// Domain=.example.com 要共享给子域时必须显式设父域
// Secure 跨站(None)时强制要求 HTTPS
// HttpOnly 禁止 JS 读取(防 XSS 窃取,但也意味着前端拿不到)
// SameSite=None 跨站必须
5. 排查:怎么确认"是 Cookie 被拒"
| 手段 | 得到 |
|---|---|
| DevTools 的 Network → Cookie 面板 | 请求是否带 Cookie |
| DevTools 的 Application → Cookies | 属性与"被阻止"标记 |
响应头 Set-Cookie |
服务端设了什么 |
| 服务端日志(Cookie 头) | 实际收到什么 |
第二项最直接:Chrome 会在"被阻止的 Cookie"里标黄并给出原因第一项(Network 面板)的用法要具体:看"请求头里有没有 Cookie",以及"响应头里 Set-Cookie 是否被接受"。两件事都要看,因为"设了但被拒"与"根本没设"表现相同。
(如 "This cookie was blocked because it was set without SameSite=None")。
第三项要看全:Set-Cookie 里的 SameSite/Secure/Domain/Path 四项任何一个不对,都会导致"设了但不生效"。
"第三方 Cookie 政策"已经是变量,而不是常量:Safari 默认阻止、Chrome 在逐步收紧、Firefox 有分区存储。所以"在我浏览器里好用"不能作为验证结论——要按"目标用户的浏览器分布"测。
// 三种浏览器策略对"嵌入登录"的影响
Safari:第三方 Cookie 默认阻止 → 嵌入页登录基本不可用
Chrome:逐步收紧 + CHIPS(分区)→ "None"不再总是有效
Firefox:分区存储(按顶层站点隔离)→ 同一用户在不同嵌入方下是两套 Cookie
// 结论:"跨站登录"应当在设计阶段就避开,而不是上线后修
6. 排错:五种"Cookie 相关"
| 现象 | 成因 | 处理方向 |
|---|---|---|
| 嵌入页登录掉线 | Lax 不带跨站 |
None; Secure 或换 token |
| 本地正常、线上失效 | 线上是 HTTPS + Secure 缺失 |
补 Secure |
| 子域拿不到 Cookie | Domain 未设父域 |
设 Domain=.example.com |
| 某路径不生效 | Path 不匹配 |
设 Path=/ |
| 登出后仍登录 | 只清了前端状态 | 服务端也要清 Cookie |
第五项(登出后仍登录)在"前后端分离"里很常见:前端清了 token,而 Cookie 还在,于是"看起来登出了",刷新又登录上。所以登出必须"服务端也清 Cookie"。
第二项很常见:本地 http://localhost 上 SameSite=None 需要 Secure,而本地没 HTTPS 时浏览器会拒——于是"本地好了、线上坏了"或反之。
最后一个"迁移"建议:从 Cookie 迁到 Header token 时,先"双读"再"单读"。也就是"服务端同时接受 Cookie 与 Bearer token",等前端全量切换后再关掉 Cookie 路径——否则"切换过程中一半用户掉线"。
// 三段式迁移(每段可回滚)
1 服务端支持 Bearer(Cookie 仍有效)
2 前端逐步切到 Bearer(灰度)
3 关闭 Cookie 认证(观察一周无异常后)
// 反例:一步切换 → 老版本前端 + 新服务端 = 全量 401
7. 边界:什么情况不必管 SameSite
| 场景 | 为什么 | 做法 |
|---|---|---|
| 纯同站应用(无嵌入、无跨站) | Lax 足够 |
默认即可 |
| 用 Header token 认证 | 与 Cookie 无关 | 不管 |
| 内部工具(单一域名) | 无跨站 | 默认 |
| 移动 App(非 WebView) | 无浏览器 Cookie | 不管 |
判断口径一句话:这个页面会不会在"另一个注册域"下被加载或提交。最后一条与"测试"有关的边界:Cookie 问题几乎无法在单元测试里覆盖,因为它依赖浏览器行为。可行的验证是"用真实浏览器在三种上下文里各测一次"(同站、跨站 iframe、跨站跳转)——十分钟能覆盖。
会(嵌入、跨站跳转、第三方登录),就必须处理 SameSite;不会(自家站点内跳转),默认值够用。
8. 自测
- 三种
SameSite值的行为差别是什么?None的两个硬约束是什么? - "同站"的判定依据是什么?端口与协议分别有什么影响?
- 五种登录失效场景里,哪两种最常被误认为"后端 bug"?
- 四种应对方案里,哪一种最彻底?它的代价是什么?
- 为什么"本地正常、线上失效"常见于 Cookie 问题?
- 判断"是否需要处理 SameSite"的那个问题是什么?