安全
什么是安全响应头?逐项说明作用与添加方法
安全响应头用来开启每个浏览器都内置的防护功能。本文逐项说明它们的作用、稳妥的初始取值,以及在常见服务器和平台上的添加方法。
本页内容
安全响应头是一类 HTTP 响应头,用来告诉浏览器开启它本来就内置的防护功能:只通过 HTTPS 连接、拒绝运行来源不明的脚本、不允许其他网站用框架嵌入本页面,等等。它们修不好有漏洞的代码,但能让跨站脚本、点击劫持、协议降级这几类常见攻击更难得手,或者在攻击发生时把损失降下来。
如何查看你的网站发送了哪些响应头
- 在浏览器中:打开开发者工具,切换到 Network 面板,刷新页面,点击第一个(文档)请求,查看 Response Headers。
- 在终端中:
curl -I https://example.com会打印出响应头。 - 使用检测工具:Rudra 的免费网站安全检测工具会检查网站是否使用 HTTPS 并把明文 HTTP 重定向过去,TLS 证书是否有效且没有临近过期,并读取 HSTS、Content-Security-Policy、X-Frame-Options(或 CSP
frame-ancestors)、X-Content-Type-Options、Referrer-Policy 和 Permissions-Policy 的取值。它还会标出暴露软件版本号的Server或X-Powered-By响应头,并检查 Cookie 标志和 CORS。
光看有没有,下不了结论。一个允许从任何地方加载脚本的 Content-Security-Policy 虽然存在,却几乎起不到保护作用,所以 Rudra 还会解析具体取值:HSTS 的 max-age 短于 180 天、CSP 的 script-src 中含有 'unsafe-inline' 或 *,或者 X-Frame-Options 的值无效,都会被报告为配置薄弱。请阅读下面各节,对照判断你自己的取值。
逐个了解这些响应头
Strict-Transport-Security (HSTS)
告诉浏览器在一段指定时间内对你的域名一律使用 HTTPS,即使有人输入的是 http:// 或点击了旧链接也不例外。这样就堵住了一个空档:否则,身处同一网络的攻击者有机会拦截第一个不安全的请求。常见的取值是 Strict-Transport-Security: max-age=63072000; includeSubDomains(两年)。
- 先用较短的
max-age(比如 300 秒),等确认每个页面都能通过 HTTPS 正常访问后再调大。 - 只有当所有子域名都提供 HTTPS 时才添加
includeSubDomains,包括那些你可能早已忘记的旧子域名。 - 加上
preload标志并提交到浏览器预加载列表后,浏览器会把你的域名必须使用 HTTPS 这一点硬编码进去。撤销起来既麻烦又缓慢,所以要放在最后,想清楚了再加。 - 浏览器只认通过 HTTPS 收到的 HSTS。
Content-Security-Policy (CSP)
一份清单,规定页面可以从哪些地方加载脚本、样式、图片、字体和框架。即使攻击者设法注入了脚本,一份好的 CSP 也能阻止浏览器运行它。它是这里功能最强的响应头,也是最容易配错的一个。对于简单的网站,可以从下面这个比较合理的配置起步:
Content-Security-Policy: default-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'
它只允许加载来自你自己源的资源(外加内联的 data: 图片),禁用插件,阻止被注入的 <base> 标签改变相对网址的指向,并禁止页面被嵌入框架。真实的网站会用到统计分析、字体、嵌入内容和支付服务商,每一项都必须明确放行;内联脚本也会被拦截,除非你用 nonce 或哈希放行。上线时先使用 Content-Security-Policy-Report-Only:浏览器会在控制台中报告违规情况(如果你配置了上报端点,也会发送到那里),但不会拦截任何内容,这样你就可以在强制执行之前调整策略。
X-Content-Type-Options
X-Content-Type-Options: nosniff 会阻止浏览器猜测文件类型,比如把一个上传的文本文件当作脚本来执行。它没有可配置的选项,而且只要你的服务器发送了正确的 Content-Type 响应头,就几乎不会造成任何问题。建议所有地方都加上。
点击劫持防护:frame-ancestors 与 X-Frame-Options
点击劫持是把你的网站藏在另一个网站的框架里,诱骗用户点击你网站上的某个东西。现代的控制手段是 CSP 指令 frame-ancestors 'none'(一律不允许嵌入)或 frame-ancestors 'self'(只允许你自己的页面嵌入)。较早的 X-Frame-Options: DENY 或 SAMEORIGIN 在旧版浏览器中起同样的作用,两者同时发送也没有坏处。如果你的页面必须嵌入到特定合作伙伴的网站上,就把这些源列在 frame-ancestors 中。
Referrer-Policy
控制访客点击链接或加载资源时,把当前网址的多少内容发送给其他网站。网址中可能包含搜索词、ID 或令牌,这些都是你不希望泄露的。Referrer-Policy: strict-origin-when-cross-origin 在站内发送完整网址,对其他网站只发送源(https://example.com),从 HTTPS 跳到 HTTP 时则什么都不发。现代浏览器已经把它作为默认值;显式设置可以让行为保持一致。
Permissions-Policy
关闭你的网站用不到的强大浏览器功能,这样无论是你自己的代码还是嵌入的第三方内容,都无法请求使用它们。对于不需要摄像头、麦克风或定位的网站:Permissions-Policy: camera=(), microphone=(), geolocation=()。
应当移除或无需设置的响应头
- 暴露版本的响应头。带版本号的
Server和X-Powered-By等于明确告诉攻击者该针对哪款软件的哪个版本下手。把它们移除,或去掉版本号(在 nginx 中使用server_tokens off;)。 - X-XSS-Protection 控制的过滤器已被现代浏览器移除。不要依赖它;可以不发送,或者把它设为
0。
如何添加安全响应头
nginx
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;add_header X-Content-Type-Options "nosniff" always;add_header Referrer-Policy "strict-origin-when-cross-origin" always;add_header X-Frame-Options "DENY" always;
always 标志会让错误响应也带上这个响应头。要当心继承问题:只要某个 location 块里出现了任何一条 add_header 指令,它就不再继承 server 层级定义的那些,于是这些网址上的响应头会悄无声息地消失。要么在块内重复一遍,要么把它们放在一个公共文件里,再用 include 引入。
Apache
启用 mod_headers 后,在虚拟主机配置或 .htaccess 中写入:Header always set X-Content-Type-Options "nosniff",其他响应头也照此格式添加,例如 Header always set Referrer-Policy "strict-origin-when-cross-origin"。
Next.js
在 next.config.js 中为所有路径返回这些响应头:async headers() { return [{ source: '/:path*', headers: [{ key: 'X-Content-Type-Options', value: 'nosniff' }, { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' }] }]; }。如果要使用带 nonce 的 CSP,则应按照 Next.js 文档的说明,改为在中间件中为每个请求生成该响应头。
Netlify、Cloudflare Pages 等托管平台
这类平台会读取发布目录中的 _headers 文件。先在一行写上路径匹配规则,例如 /*,再在下方缩进,每行写一个响应头,例如 X-Content-Type-Options: nosniff。在 Vercel 上,对应的是 vercel.json 中的 headers 部分。如果你使用了 CDN,通常也可以由它来添加响应头。
稳妥地上线
- 先添加低风险的响应头:
X-Content-Type-Options、Referrer-Policy、Permissions-Policy以及点击劫持防护。 - 用较短的
max-age启用 HSTS,然后在几周内逐步调大。 - 以仅报告模式部署 CSP,修复它报告的问题,然后再强制执行。
- 测试涉及第三方的流程:登录、结账和支付框架、嵌入的视频、地图、聊天组件和统计分析。
- 用安全检测工具复查一遍;以后每次新增第三方工具时,都再查一次。
常见错误
- 从别的网站照搬一份严格的 CSP,结果把支付、统计分析或嵌入内容弄坏了。
- 写出一份满是
'unsafe-inline'、'unsafe-eval'和通配符的 CSP,能通过“是否存在”的检查,却起不到多少保护作用。 - 还没等所有子域名都做好 HTTPS 准备,就加上了 HSTS
preload。 - 在 HTML 的
<meta>标签里设置响应头。只有部分 CSP 指令可以这样用;HSTS、frame-ancestors和X-Frame-Options写在 meta 标签里会被忽略。 - 在应用和代理两处都添加了响应头,导致响应中出现重复或互相冲突的值。
- 把响应头当成更新软件、校验输入和转义输出的替代品。
响应头和响应头检查覆盖不到的地方
响应头只是其中一层防护。响应头检查发现不了过时的插件、弱的管理员密码、注入漏洞或暴露在外的备份文件。Cookie 也值得单独检查:会话 Cookie 应带有 Secure、HttpOnly 和 SameSite 属性。Rudra 的安全检测工具会报告页面自身响应所设置 Cookie 的这些标志(只报告名称,绝不涉及值);详情请参阅如何检查 Cookie 安全性,而被动检查能告诉你什么、不能告诉你什么,请参阅如何检查网站安全。如果想更全面地了解网站的健康状况,可以运行一次完整的网站审核。
常见问题
应该先添加哪个安全响应头?
先确保整个网站都通过 HTTPS 提供服务,然后添加 X-Content-Type-Options: nosniff、Referrer-Policy 和点击劫持防护,它们几乎不会造成任何问题。接着以较短的 max-age 添加 HSTS,最后才是 Content-Security-Policy,并且要先用仅报告模式。
安全响应头会影响 SEO 吗?
不会直接影响。HTTPS 本身是 Google 的一个轻量级排名信号,但 CSP 或 X-Content-Type-Options 这类响应头并不影响排名。它们保护的是你的访客和网站声誉,这一点更重要。
X-Frame-Options 过时了吗?
它已被更灵活的 CSP frame-ancestors 指令取代。两者同时发送没有坏处,还能照顾到旧版浏览器。
可以用 meta 标签设置安全响应头吗?
只能部分实现。Content-Security-Policy 可以写在 meta 标签里,但无法使用 frame-ancestors 或上报功能。HSTS、X-Frame-Options 和其他大多数安全响应头只有作为真正的 HTTP 响应头才会生效。
严格的 Content-Security-Policy 会把我的网站弄坏吗?
有可能,如果它拦截了页面所需的脚本、样式或框架。所以应当先从 Content-Security-Policy-Report-Only 开始,查看报告的违规情况,调整策略,然后再强制执行。