X-Frame-Options必须由服务端在HTTP响应头中设置才有效,<meta>标签和JS检测均无效;现代浏览器完全忽略<meta http-equiv="X-Frame-Options">,防御唯一可靠路径是服务端发送X-Frame-Options或CSP的frame-ancestors指令。

点击劫持不是前端代码能“修好”的问题,而是必须由服务端控制页面是否允许被 <iframe> 嵌套。靠 JS 检测 window.top 或写 <meta> 标签基本无效,浏览器根本不认。
为什么 X-Frame-Options 不能写在 HTML meta 里
浏览器只从 HTTP 响应头读取 X-Frame-Options,HTML 中的 <meta http-equiv="X-Frame-Options" content="DENY"> 完全被忽略——这是规范明确规定的,不是兼容性问题,是根本不起作用。
- 所有主流浏览器(Chrome、Firefox、Safari、Edge)均不支持 meta 方式设置该 header
- 哪怕你看到页面源码里写了,Network 面板里也看不到这个 header 被发出,就等于没设
- 如果后端没配,光靠前端加 meta,攻击者用本地
test.html+<iframe src="your-page">一试就穿
如何验证 frame-ancestors 是否生效
直接打开浏览器开发者工具的 Network 标签页,刷新敏感页面(如支付页、密码修改页),点开任意一个 HTML 响应,看 Response Headers 里有没有 Content-Security-Policy 字段,且包含 frame-ancestors 'none' 或 frame-ancestors 'self'。
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
-
'none'和'self'必须带单引号,写成frame-ancestors none是无效语法 - 如果同时设置了
X-Frame-Options和frame-ancestors,后者优先,前者被忽略 - 验证时别只看页面是否“显示”,重点是 iframe 是否静默加载成功:本地建个 test.html 加透明 iframe,能加载出来就说明没拦住
Nginx / Apache / Django 的典型配置差异
不同环境写法细节差别不小,错一个字符就失效。
立即学习“前端免费学习笔记(深入)”;
- Nginx:必须加
always参数,否则 304 响应不带 header ——add_header X-Frame-Options "DENY" always; - Apache:用
Header always set X-Frame-Options "DENY",需启用headers_module - Django:取消注释
django.middleware.clickjacking.XFrameOptionsMiddleware,再设X_FRAME_OPTIONS = 'DENY';若用 CSP,则通过SECURE_CONTENT_TYPE_NOSNIFF = True等配套项协同
最常被忽略的一点:登录态页面、API 返回 HTML 的接口、甚至 404 页面,只要可能被嵌入并触发操作,就必须统一加防护。漏掉任意一个路径,攻击链就成立。


















