两道门: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.comb.xxx.com
X-Frame-Options: SAMEORIGIN 严格同源(协议+完整域名+端口) ❌ 不算同源,会被拦
Cookie 的 SameSite 注册域相同即可(俗称"同站") ✅ 算同站,Lax/Strict 都能正常带 Cookie

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

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

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

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

    有 → 卡在第一关,去看目标网站的响应头里 X-Frame-OptionsContent-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.comapp.xxx.com),这样 Cookie 层面天然是"同站",不需要改成 None,唯一还要处理的就只剩 frame-ancestors 这一关了。

相关推荐
circuitsosk1 小时前
Python 模块与包管理:import 机制、虚拟环境与 pip 完全指南
开发语言·python·pip·依赖管理·模块与包
fl1768311 小时前
基于C#WPF实现的内存加速球清理类似360加速球内存清理
开发语言·c#·wpf
weixin_440730501 小时前
python+request实现接口-小结
开发语言·python
ZJU_统一阿萨姆2 小时前
【算子开发】扫描(Scan)与前缀和
开发语言·arm开发·架构·系统架构·硬件架构
-凌凌漆-2 小时前
【C语言】结构体typedef struct与struct的区别
c语言·开发语言
Web3_Basketball2 小时前
动手玩DeepSeek视觉版:多模态OCR实战代码
ai编程
for_ever_love__2 小时前
PySpark学习: 数据输出
开发语言·python·学习·spark
言乐63 小时前
Python实现自动拉人脚本示例
开发语言·windows·python·django·pygame
啊啊啊啊啊!!!!3 小时前
【c++】二叉搜索树
开发语言·c++