X-Frame-Options必须在输出前设置且不可缓存覆盖,推荐与Content-Security-Policy frame-ancestors共存以兼顾兼容性,框架中间件统一注入最可靠,CDN/代理需额外校验。

header('X-Frame-Options: DENY') 必须放在输出前,且不能被缓存覆盖
PHP的X-Frame-Options头必须在任何内容输出前调用header(),否则会报“headers already sent”错误。常见踩坑点是:模板里提前echo了空格、BOM头残留、或require了含输出的文件。
它不支持动态条件判断(比如只对登录页禁用iframe),因为浏览器在加载页面前就检查该头——所以别写if ($isSensitivePage) header('X-Frame-Options: DENY');这种逻辑,除非你100%确认所有路径都走同一入口文件并统一拦截。
- Apache用户需确认
mod_headers已启用,否则PHP的header()可能被服务器层覆盖 - Nginx用户更稳妥的做法是在server块里加:
add_header X-Frame-Options "DENY" always;,避免PHP层遗漏 - 注意CDN或反向代理(如Cloudflare)可能默认剥离或重写该头,要检查最终响应中是否真实存在
Content-Security-Policy 的 frame-ancestors 比 X-Frame-Options 更灵活但兼容性差
frame-ancestors是CSP 2.0标准,支持白名单域名(如'self' https://trusted.com'),还能配合report-uri收集违规尝试。但它在IE和旧版Edge上完全无效,而X-Frame-Options在这些浏览器中仍能生效。
两者同时设置时,浏览器优先遵循更严格的策略。但别指望它们叠加增强——现代浏览器只认frame-ancestors,老浏览器只认X-Frame-Options,所以必须共存:
立即学习“PHP免费学习笔记(深入)”;
header("X-Frame-Options: DENY");
header("Content-Security-Policy: frame-ancestors 'none';");
注意:frame-ancestors 'none'等价于X-Frame-Options: DENY,但写'self'时语义不同:前者允许同源嵌套,后者要求**完全同源**(协议+域名+端口一致),这点常被忽略。
前端JS防劫持(frame busting)只是补救,且容易被绕过
像if (top !== self) top.location = self.location;这类脚本,在现代浏览器中基本失效。攻击者只需在iframe上加sandbox="allow-scripts"属性,或用document.domain降域就能绕过。
它唯一适用场景是:目标用户群体大量使用IE8–10,且后端无法控制HTTP头(比如静态HTML托管在第三方CDN)。但即便如此,也要配合X-Frame-Options——JS只是fallback,不是主力防线。
- 不要把这段JS放在
<body>底部,必须放在<head>最前面,否则页面已渲染出UI再跳转,用户可能看到闪屏 - 避免用
window.top.location.href,改用window.top.location.replace()防止浏览器历史栈污染 - 若站点启用了
Content-Security-Policy: sandbox,这段JS会被直接阻止执行
PHP框架路由中间件里统一注入安全头最可靠
在Laravel、Symfony或ThinkPHP等框架中,把安全头逻辑写进全局中间件,比在每个控制器里重复header()更可控。例如Laravel的TrustProxies中间件之后加一个SecurityHeaders:
public function handle($request, Closure $next) {
$response = $next($request);
$response->headers->set('X-Frame-Options', 'DENY');
$response->headers->set('Content-Security-Policy', "frame-ancestors 'none';");
return $response;
}
这样能确保所有响应(包括API JSON、图片返回、甚至404页面)都带上头。但要注意:如果某些接口明确需要被嵌入(如OAuth回调页),得在中间件里加白名单逻辑,否则会破坏正常流程。
真正容易被忽略的是AJAX请求——它们虽然不被iframe加载,但X-Frame-Options对XMLHttpRequest无影响;而CSP的frame-ancestors也只约束frame/iframe/embed,不影响fetch。所以别误以为加了头就万事大吉,关键还是看哪个页面实际承载了敏感操作按钮。

















