为什么需要安全响应头

HTTP 安全响应头告诉浏览器如何安全地处理网站内容。配置得当可以防御多种常见攻击,比如:攻击者在评论区塞进一段脚本,浏览器看到 Content-Security-Policy 禁止内联脚本后直接拒绝执行;第三方站点把你的登录页嵌进 iframe 里伪造界面,X-Frame-Options 会让浏览器拒绝加载;某张图片其实是一段 HTML,X-Content-Type-Options: nosniff 阻止浏览器自作主张地"嗅探"内容类型。

这些头不会拖慢网站速度,不需要改业务代码,成本几乎为零,却能堵住一整套攻击面。下面是 Nginx 和 Apache 的完整配置,以及每个头的说明。

Nginx 完整安全头配置

# /etc/nginx/nginx.conf 或站点配置的 server 块

# === 核心安全头 ===

# 防止点击劫持
add_header X-Frame-Options "SAMEORIGIN" always;

# 防止 MIME 类型嗅探
add_header X-Content-Type-Options "nosniff" always;

# 启用 XSS 过滤(已废弃但向下兼容)
add_header X-XSS-Protection "0" always;

# 控制引荐来源信息
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# 权限控制(限制 API 访问)
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()" always;

# === HSTS(HTTP Strict Transport Security)===
# 确认 HTTPS 正常工作后再启用
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

# === CSP(Content Security Policy)===
# 根据实际需求调整,过于严格可能影响功能
add_header Content-Security-Policy "
  default-src 'self';
  script-src 'self' 'unsafe-inline' 'unsafe-eval' https:;
  style-src 'self' 'unsafe-inline' https:;
  img-src 'self' data: https:;
  font-src 'self' data: https:;
  connect-src 'self' https:;
  frame-ancestors 'self';
  form-action 'self';
  base-uri 'self';
" always;

Apache 配置

# .htaccess 或 httpd.conf

# 核心安全头
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"

各安全头说明

响应头 作用 推荐值 风险
X-Frame-Options 防止点击劫持(页面被嵌入 iframe) SAMEORIGIN
X-Content-Type-Options 防止 MIME 嗅探攻击 nosniff
Strict-Transport-Security 强制 HTTPS 连接 max-age=63072000; includeSubDomains
Referrer-Policy 控制 HTTP Referer 头信息 strict-origin-when-cross-origin
Permissions-Policy 控制浏览器 API 权限 按需限制
Content-Security-Policy 防止 XSS 和数据注入 按站点配置

各安全头的深层含义

X-Frame-Options 是最老的防点击劫持手段:SAMEORIGIN 表示只有同源页面才能用 iframe 嵌入你的页面。新版浏览器还支持 frame-ancestors 指令(写在 CSP 里),两者并存时以 CSP 为准,所以配置时最好两个都写上。

Strict-Transport-Security(HSTS)的作用是告诉浏览器"这个域名只准走 HTTPS",从而掐断第一次 HTTP 请求时的降级风险。max-age=63072000 是 730 天,includeSubDomains 覆盖子域名,preload 则把域名提交进浏览器内置的 HSTS 预载列表。注意:一旦启用 HSTS,浏览器会强制 HTTPS,如果某个子域名还没配好证书,用户将无法访问——这也是它风险列为"中"的原因。

Referrer-Policy 控制页面跳转时把多少来源信息带给对方。strict-origin-when-cross-origin 是安全与实用之间的默认平衡点:同源跳转带完整地址,跨域只带域名,从 HTTPS 跳 HTTP 则完全不带。

Permissions-Policy 取代了老的 Feature-Policy,用来限制 cameramicrophonegeolocation 这类浏览器 API。给用不到的能力一律设空,能明显缩小恶意脚本可调用的攻击面。

Content-Security-Policy 是最复杂也最强大的一头,直接影响页面能否正常运行。CSP 用指令逐类声明资源来源:script-src 管脚本、style-src 管样式、img-src 管图片、connect-src 管 fetch/XHR。配置过严会让内联脚本、CDN 资源甚至第三方统计全部失效,所以一定要"先报告后收紧"。

部署顺序也值得讲究:先在测试环境全量开启,观察一周内的功能回归和控制台报错,再灰度到生产。头一旦发出去,浏览器端会记住 HSTS 这类状态,回滚并不即时,所以"小步快走、逐步加严"远比一次到位稳妥。特别是 CSP,从宽松版切到严格版之间,最好留出用 Content-Security-Policy-Report-Only 模式收集一段时间违规报告的过渡期,看看真实页面到底触发了哪些拦截,再决定哪些域名要加白。

CSP 配置指南

宽松策略(适合大多数网站)

add_header Content-Security-Policy "
  default-src 'self';
  script-src 'self' 'unsafe-inline' https:;
  style-src 'self' 'unsafe-inline' https:;
  img-src 'self' data: https:;
  font-src 'self' data: https:;
  connect-src 'self' https:;
  frame-ancestors 'self';
  form-action 'self';
" always;

严格策略(适合安全敏感站点)

add_header Content-Security-Policy "
  default-src 'none';
  script-src 'self';
  style-src 'self';
  img-src 'self';
  connect-src 'self';
  frame-ancestors 'none';
  form-action 'self';
  base-uri 'self';
" always;

验证工具

  • securityheaders.com — 安全头评级
  • CSP Evaluator — CSP 策略评估
  • SSL Labs — SSL/TLS 配置评级
  • curl -sI https://example.com | grep -i "^\(x-\|strict\|content\|referrer\|permissions\)" — 本地检查

配置后如何一步步验证

  1. 先用 curl -sI https://你的域名 | grep -i "content-security\|strict-transport\|x-frame\|x-content-type\|referrer-policy\|permissions-policy" 确认所有头都已返回;
  2. 打开 securityheaders.com 输入域名,看评级是否到 A 或 A+;
  3. 用 CSP Evaluator 粘贴你的 CSP 策略,检查是否有明显漏洞(比如把 'unsafe-inline' 和外部脚本混用);
  4. 最后在真实浏览器里把网站主要页面、登录流程、支付流程各走一遍,确认功能没被误伤。

常见问题与排错

启用 HSTS 后子域名访问不了怎么办? 先确认该子域名已配好 HTTPS 证书,再启用 includeSubDomains;紧急情况下把 HSTS 删掉后,浏览器端缓存最长还要等 max-age 到期才失效,所以线上改动要格外谨慎。

CSP 太严,页面样式全乱了? 把策略切回"宽松版",或者用 report-uri/report-to 先收集违规报告,看哪些资源被拦截,再逐一加白名单,最后收紧。

为什么加了头但 curl 看不到? 检查 Nginx 的 add_header 是否写在 location 块里——location 内的 add_header 会覆盖 server 级的同名头;Apache 则要确认 mod_headers 已启用。

一个真实案例

某内容站被反复注入恶意脚本,攻击者把 <script src="https://evil.example/x.js"> 塞进文章正文。开启严格 CSP(script-src 'self')后,这类外链脚本直接被浏览器拒绝执行,配合 X-Content-Type-Options: nosniffReferrer-Policy,同类注入的攻击面基本被关闭。整个改动只花了一个小时,线上零故障。

配置检查清单

  • X-Frame-Options: SAMEORIGIN
  • X-Content-Type-Options: nosniff
  • Strict-Transport-Security 已配置
  • Referrer-Policy 已配置
  • Permissions-Policy 已配置
  • Content-Security-Policy 已配置并测试
  • securityheaders.com 评级 A 或 A+
  • 配置后测试网站功能正常