两道门:X-Frame-Options 和 SameSite 到底谁管什么

两道门:X-Frame-Options 和 SameSite 到底谁管什么

如果你做过跨系统嵌入(比如把 B 系统用 iframe 塞进 A 系统的页面),大概率被这两个东西坑过。很多人分不清它们的区别,遇到问题就一股脑地去调 Cookie,结果发现页面根本没显示出来;或者好不容易把 iframe 显示出来了,又发现里面一直提示"未登录"。

这篇文章把这两个概念彻底拆开讲清楚:它们管的根本不是同一件事。

一句话区分

X-Frame-Options 管的是"这个网页能不能被装进别人的 iframe 里"。

SameSite 管的是"跨站请求时,浏览器要不要自动带上 Cookie"。

一个卡在"显示"这一关,一个卡在"身份识别"这一关。就算你把第一关打通了,第二关照样能把你拦下来------这也是为什么很多人调半天,页面明明显示出来了,却一直是未登录状态。

第一道门:X-Frame-Options ------ 网站能不能被嵌入

这是一个 HTTP 响应头,由被嵌入的那个网站的服务器 返回,告诉浏览器:"我这个页面,允许不允许被放进别人的 <iframe> 里显示"。

它只有三个可选值:

值 含义
DENY 谁都不能嵌入,自己嵌自己都不行
SAMEORIGIN 只有同源(协议+域名+端口完全一致)的页面能嵌入
ALLOW-FROM https://xxx.com 只允许指定来源嵌入(已废弃,现代浏览器不再支持)

这里有个很多人会踩的坑:SAMEORIGIN 判断的是严格同源 ,子域名不算同源。app.example.com 想嵌入 portal.example.com,即使看起来是"同一家系统",在浏览器眼里也是两个不同的来源,一样会被拦截。

现在业界更推荐用 CSP(内容安全策略)里的 frame-ancestors 指令来替代它,因为它支持写多个域名,还支持通配符匹配子域名:

复制代码
Content-Security-Policy: frame-ancestors 'self' https://*.example.com;

如果两者同时出现,浏览器只认 frame-ancestors,X-Frame-Options 会被忽略------这是浏览器规范明确规定的优先级。

一旦这道门被关上,浏览器会直接拒绝渲染整个 iframe,你会在控制台看到类似这样的报错:

复制代码
Refused to display 'https://xxx.com/' in a frame because it set
'X-Frame-Options' to 'sameorigin'.

这时候 iframe 区域是一片空白,连页面骨架都出不来------这一步失败了,后面讨论 Cookie 的问题根本无从谈起。

假设第一道门已经过了,iframe 里的页面成功显示出来了。接下来它要正常工作,通常需要知道"这个用户是谁",也就是要带上登录用的 Cookie 一起发请求。这时候就轮到 SameSite 登场了。

SameSite 是 Cookie 的一个属性,由服务器在 Set-Cookie 时指定,控制的是:当请求是"跨站"发起的时候,浏览器要不要自动把这个 Cookie 带上。

三个可选值:

值 行为
Strict 只有同站发起的请求才带 Cookie,跨站一律不带
Lax(现代浏览器默认值) 部分跨站场景(比如点链接跳转)会带,但 iframe 内部发起的子请求、跨站 POST 等不带
None(必须搭配 Secure) 允许跨站请求携带 Cookie,包括 iframe 内的请求

关键就在这里:iframe 里加载的内容,对浏览器来说就是一次"第三方请求" 。如果这个网站的登录 Cookie 是默认的 Lax(现在几乎所有浏览器都默认这个值),那 iframe 里发起的接口请求统统不会带上 Cookie。表现出来就是:页面框架显示出来了,但一发请求就是未登录、401、跳登录页。

两道门的"站"概念还不一样

这里还有个容易混淆的细节:两者对"同不同源/同不同站"的判定标准并不统一。

机制 判定粒度 举例:a.xxx.com 和 b.xxx.com
X-Frame-Options: SAMEORIGIN 严格同源(协议+完整域名+端口) ❌ 不算同源,会被拦
Cookie 的 SameSite 注册域相同即可(俗称"同站") ✅ 算同站,Lax/Strict 都能正常带 Cookie

也就是说,同样两个子域名之间,Cookie 层面可能完全没问题,但 iframe 显示这一关如果对方设的是严格的 SAMEORIGIN,照样会被拦下来。两道门要分开检查,不能因为一个通了就假设另一个也没事。

排查思路:出问题先看是卡在哪一关

遇到"嵌入失败"或者"嵌入后功能不正常",可以按这个顺序排查:

  1. 打开控制台,看有没有 Refused to display ... in a frame 这类报错。

    有 → 卡在第一关,去看目标网站的响应头里 X-Frame-Options 或 Content-Security-Policy: frame-ancestors 设的什么值,需要目标网站服务端加白名单放行你的域名。

  2. iframe 能正常显示,但页面提示未登录、接口报 401/403。

    没有第一关的报错 → 大概率是第二关的问题,去看目标网站登录用的 Cookie 是不是 SameSite=Lax(默认值)或 Strict。如果是,需要目标网站把这个 Cookie 改成 SameSite=None; Secure(必须全站 HTTPS 才能生效)。

  3. 两关都没问题,依然显示未登录。

    检查一下登录态是不是走的 localStorage/sessionStorage 或者前端持有的 Token,而不是 Cookie------这类存储方式和 SameSite 完全无关,是另一套排查逻辑了。

一个常被忽略的细节:重定向响应上的头不算数

还有一种容易让人误判的情况:如果 iframe 请求的地址先经过了一次 302 跳转(比如先到一个鉴权中转页,再跳到真正的内容页),这个中间的跳转响应 即使设了 X-Frame-Options: DENY,也不会生效------因为它的 body 是空的,浏览器根本没有内容可渲染,自然谈不上"渲染并拦截"。真正起作用的,永远是浏览器最终渲染显示的那个响应的头,和请求链路里经过了几次跳转、中间页设了什么头没有关系。

最稳妥的落地方案

如果这是你自己能控制、要长期稳定运行的系统,不建议依赖浏览器的各种"信任豁免"机制(比如用户先手动登录过某个域名后带来的临时 Cookie 豁免),这些机制不受你控制,且不同浏览器实现差异很大,未来也可能随时被收紧。真正稳定的方案是让被嵌入的系统同时改好两处配置:

复制代码
# 允许被指定域名嵌入
Content-Security-Policy: frame-ancestors 'self' https://你的域名.com;

# 允许跨站携带登录 Cookie(必须是 HTTPS)
Set-Cookie: session=xxx; SameSite=None; Secure

如果条件允许,更彻底的方案是把两个系统统一到同一个注册域名下 (比如 portal.xxx.com 嵌 app.xxx.com),这样 Cookie 层面天然是"同站",不需要改成 None,唯一还要处理的就只剩 frame-ancestors 这一关了。

相关推荐
java资料站2 小时前
十一、评估测试
开发语言·人工智能·python
左手の明天2 小时前
SLIP 协议封装与解析详解:原理、C 代码与实战案例
linux·c语言·开发语言·c++
海上小飞龙2 小时前
【KMP算法-下篇】同一道题:Java 库函数 2600 微秒,手写 KMP 16 微秒
java·开发语言·算法
朝朝辞暮i3 小时前
C++ 第 12 课:局部变量、作用域、变量生命周期
开发语言·c++·算法
狗凯之家源码网3 小时前
淘客口令落地页生成器 PHP 版深度评测
开发语言·php
画绛集美术3 小时前
本地化部署LLM用于美术史问答:一间美术教室的实践记录
开发语言·php
八解毒剂3 小时前
【计组】中央处理器CPU
java·开发语言·计算机组成原理
FakeOccupational4 小时前
【电路笔记 信号】DBPSK 波形查找表+脉冲成形(升余弦+根升余弦滤波)
开发语言·笔记
打工仔折腾 AI4 小时前
工业场景下时序库与实时计算一体化架构选型实践对比
java·开发语言·后端·python·性能优化·架构·ai agent 实战
ZealSinger5 小时前
Java虚拟线程上线后瓶颈转移到哪看三层
java·开发语言·firefox