一、引言
很多 Web 安全事故不是被「黑」进来的,而是基础防御没配好:没设 CSP 导致 XSS 落地、没开 HSTS 让流量可被降级、没加 X-Frame-Options 被点击劫持。安全响应头是成本极低、收益极高的一层防御——改几行配置,就能把一整类攻击挡在门外。
本文从安全响应头全景讲起,重点拆解 CSP 与 HSTS 的配置与排障,覆盖 XSS 纵深、SRI 完整性校验,最后给出在 Cloudflare / Vercel / 边缘函数里统一落地安全头的方案。
二、安全响应头全景
2.1 一张表看懂核心安全头
| 响应头 | 作用 | 防什么 |
|---|---|---|
Content-Security-Policy | 声明可加载资源的白名单 | XSS、数据注入 |
Strict-Transport-Security | 强制 HTTPS 一段时间 | SSL 剥离、降级攻击 |
X-Frame-Options | 禁止/限制 iframe 嵌入 | 点击劫持 |
X-Content-Type-Options | 禁止 MIME 嗅探 | 类型混淆攻击 |
Referrer-Policy | 控制 Referer 携带范围 | 信息泄露 |
Permissions-Policy | 控制浏览器功能权限 | 摄像头/定位滥用 |
Cross-Origin-Opener-Policy | 隔离浏览上下组 | Spectre 类侧信道 |
2.2 一份基线安全头
HTTP/2 200
content-security-policy: default-src 'self'; script-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'
strict-transport-security: max-age=31536000; includeSubDomains; preload
x-frame-options: DENY
x-content-type-options: nosniff
referrer-policy: strict-origin-when-cross-origin
permissions-policy: camera=(), microphone=(), geolocation=()
心法:先上「保守基线」,再逐步放宽。把安全头当「代码」,改了就测试、就灰度,别一次性上最严策略把线上资源全拦截。
三、CSP 内容安全策略:XSS 的主闸门
3.1 CSP 的运作原理
CSP 用 default-src / script-src / img-src 等指令声明「页面允许从哪些源加载哪些资源」。浏览器执行时,不在白名单内的请求被直接拦截——XSS 注入的脚本无处执行。
CSP 不修漏洞,但把漏洞的「可用性」打掉:
即使攻击者注入了一段 <script>,CSP 也会因不在白名单而拒绝执行
3.2 从零到一的 CSP 三步法
第一步(开发期,只报告不拦截):
content-security-policy-report-only: default-src 'self'; script-src 'self'; report-uri /csp-report
第二步(观察报告,修正白名单):
在 report 里看哪些合法资源被拦 → 补进白名单
第三步(正式启用):
content-security-policy: default-src 'self'; script-src 'self'; report-uri /csp-report
3.3 CSP 与第三方脚本的冲突
# 需要引入第三方脚本时的 CSP:
content-security-policy:
default-src 'self';
script-src 'self' https://cdn.example.com 'nonce-abc123';
img-src 'self' data: https://img.example.com;
style-src 'self' 'unsafe-inline';
connect-src 'self' https://api.example.com;
frame-ancestors 'none'
<!-- 配合 nonce:只放行带此 nonce 的内联脚本 -->
<script nonce="abc123">
window.analytics = { enabled: true }
</script>
细节:
'unsafe-inline'会让 CSP 的 script 保护大打折扣,能用nonce或hash('sha256-...')就不要用'unsafe-inline'。这是 XSS 防御中最关键的取舍。
3.4 CSP 常见报错与排障
报错一:Refused to load the script ... because it violates CSP
原因:script-src 白名单没有该源
处理:加入合法源,或用 nonce
报错二:Refused to connect to ...
原因:fetch/XHR 目标不在 connect-src
处理:补 connect-src(常见于调第三方 API)
报错三:violates the following directive: "script-src"
原因:内联脚本无 nonce/hash 且无 unsafe-inline
处理:加 nonce 或改外部文件
// 上报 CSP 违规到自建端点,便于持续观测
addEventListener('securitypolicyviolation', (e) => {
fetch('/csp-report', {
method: 'POST',
body: JSON.stringify({
blocked: e.blockedURI,
directive: e.effectiveDirective,
source: e.sourceFile,
line: e.lineNumber,
}),
})
})
四、HSTS:强制 HTTPS 的护城河
4.1 HSTS 语义
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
max-age :告诉浏览器「多久内只走 HTTPS」(单位秒)
includeSubDomains:子域一并强制
preload :申请加入浏览器内置 HSTS 预加载列表
浏览器收到 HSTS 后,在有效期内拒绝任何明文 HTTP 请求,从源头切断「SSL 剥离」攻击——攻击者无法把 HTTPS 降级成 HTTP 窃听。
4.2 上线 HSTS 的顺序
阶段一:max-age=0(关闭),确认全站 HTTPS 无错
阶段二:max-age=300(短时间),观察有没有依赖 HTTP 的旧客户端
阶段三:max-age=31536000; includeSubDomains(正式启用)
阶段四:评估加入 preload 列表(需先满足预加载要求)
铁律:
includeSubDomains会把策略覆盖到所有子域——如果某个子域还不支持 HTTPS,会被用户访问不了。启用前先清点子域。
4.3 别忘「双保险」:HTTP 跳转 + 缓存清理
// 边缘层:HTTP 请求一律 301 跳 HTTPS,并带上 HSTS
export default {
async fetch(request) {
const url = new URL(request.url)
if (url.protocol === 'http:') {
url.protocol = 'https:'
return Response.redirect(url.toString(), 301)
}
const response = await fetch(request)
const h = new Headers(response.headers)
h.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains')
return new Response(response.body, { status: response.status, headers: h })
},
}
五、XSS 纵深防御:响应头只是其中一层
5.1 XSS 的完整防线
第一层:输入验证与输出编码(根本)
服务端对用户输入做校验,渲染时按上下文编码(HTML/属性/JS/URL 各自编码)
第二层:CSP 白名单(兜底)
即使有注入点,恶意脚本也无法执行
第三层:Cookie 安全属性(防窃取)
HttpOnly / Secure / SameSite,XSS 即使成功也拿不到会话
# Cookie 安全属性(从响应头看)
set-cookie: session=abc; HttpOnly; Secure; SameSite=Lax; Path=/
5.2 SameSite 与 CSRF
SameSite=Lax :跨站请求不携带 Cookie(默认安全)
SameSite=Strict:更严格,跨站一律不带
SameSite=None :必须配 Secure,用于第三方登录等场景
心法:安全响应头是「纵深防御」的一环,不是全部。CSP + HttpOnly Cookie + SameSite + 输入输出编码,四层叠起来才能应对真实世界的 XSS/CSRF 组合攻击。完整的认证与 Cookie 安全实践可参考 边缘认证与会话管理。
六、SRI 子资源完整性:CDN 文件可信校验
6.1 SRI 解决的问题
从第三方 CDN 加载 jquery.js 或 sdk.js,如果 CDN 被入侵或文件被篡改,页面会静默执行恶意代码。SRI 用「文件哈希白名单」解决这个问题:浏览器加载前先校验哈希,不匹配就拒绝执行。
<!-- 正常加载:integrity 指定哈希 -->
<script
src="https://cdn.example.com/sdk.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxRZ/2vM4V4wQ5R3+s+xv3YbQ"
crossorigin="anonymous"
></script>
6.2 生成 integrity 哈希
# 生成 SRI 哈希(sha384 是常见选择)
openssl dgst -sha384 -binary sdk.js | openssl base64 -A
# 或用 npm 工具
npx sri-toolbox sha384 ./sdk.js
6.3 SRI 的注意点
- integrity 变化:第三方文件更新 → 哈希要同步更新,否则直接失效
- crossorigin="anonymous":跨域资源必须带,否则 SRI 校验会被跳过
- 动态内容不可用:SRI 只适合静态文件
七、在平台与边缘统一落地安全头
7.1 Cloudflare:Transform Rules 全局加头
# Cloudflare Transform Rules(Response Header Modification)
# 对所有响应统一注入安全头
# - 优先级高的规则先执行,可用表达式限定路径
// 或用 Worker 统一注入(可编程、更灵活)
export default {
async fetch(request) {
const response = await fetch(request)
const headers = new Headers(response.headers)
const csp = [
"default-src 'self'",
"script-src 'self'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data:",
"connect-src 'self'",
"frame-ancestors 'none'",
].join('; ')
headers.set('Content-Security-Policy', csp)
headers.set('X-Frame-Options', 'DENY')
headers.set('X-Content-Type-Options', 'nosniff')
headers.set('Referrer-Policy', 'strict-origin-when-cross-origin')
return new Response(response.body, { status: response.status, headers })
},
}
7.2 Vercel:Next.js headers 配置
// next.config.js
const securityHeaders = [
{ key: 'Content-Security-Policy', value: "default-src 'self'; script-src 'self'" },
{ key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains' },
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
]
module.exports = {
async headers() {
return [{ source: '/(.*)', headers: securityHeaders }]
},
}
7.3 统一策略的分级
全局安全头(所有响应都加):
X-Frame-Options / X-Content-Type-Options / Referrer-Policy
按页面分级(路由级):
高安全页面(登录/支付):更严 CSP
内容发布页:允许第三方嵌入资源
测试期单独放开:
用 CSP-Report-Only 灰度,别影响现有功能
八、总结
Web 安全加固的落地要点:
- 基线先行:X-Frame-Options、X-Content-Type-Options、Referrer-Policy、Permissions-Policy 四件套先铺满。
- CSP 分级推进:Report-Only 观察 → 修正白名单 → 正式启用,别一步到位。
- script-src 用 nonce 不用 unsafe-inline:这是 XSS 防御最关键的一处取舍。
- HSTS 渐进启用:max-age 从小到大,includeSubDomains 前先清点子域,成熟后进 preload。
- 纵深而非单点:CSP + HttpOnly/SameSite Cookie + 输入输出编码叠加,才挡得住真实 XSS。
- SRI 管第三方:外部 CDN 脚本一律配 integrity,CDN 被入侵也不至于被投毒。
- 平台统一注入:Cloudflare Transform Rules / Worker、Vercel headers 配置,全局一处管理,别让每个页面自己加。
安全响应头是所有安全体系里「性价比最高的第一道门」——投入几行配置,挡住的是一整类攻击面。配合 边缘认证与会话管理 的会话保护与 域名与 DNS 接入 的 HTTPS 签发链路,你的 Web 应用才算把「进门安全」做扎实。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。