Web 安全加固:CSP、HSTS、安全响应头与 XSS 防护实战

Web 安全加固深度实战:安全响应头全景(CSP/HSTS/X-Frame-Options/Referrer-Policy 等)、CSP 策略设计与常见报错排障、HSTS 与 TLS 升级、XSS 纵深防御、SRI 子资源完整性校验,以及在 Cloudflare/Vercel/边缘层统一落地安全头的方案。

一、引言

很多 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 安全加固的落地要点:

  1. 基线先行:X-Frame-Options、X-Content-Type-Options、Referrer-Policy、Permissions-Policy 四件套先铺满。
  2. CSP 分级推进:Report-Only 观察 → 修正白名单 → 正式启用,别一步到位。
  3. script-src 用 nonce 不用 unsafe-inline:这是 XSS 防御最关键的一处取舍。
  4. HSTS 渐进启用:max-age 从小到大,includeSubDomains 前先清点子域,成熟后进 preload。
  5. 纵深而非单点:CSP + HttpOnly/SameSite Cookie + 输入输出编码叠加,才挡得住真实 XSS。
  6. SRI 管第三方:外部 CDN 脚本一律配 integrity,CDN 被入侵也不至于被投毒。
  7. 平台统一注入:Cloudflare Transform Rules / Worker、Vercel headers 配置,全局一处管理,别让每个页面自己加。

安全响应头是所有安全体系里「性价比最高的第一道门」——投入几行配置,挡住的是一整类攻击面。配合 边缘认证与会话管理 的会话保护与 域名与 DNS 接入 的 HTTPS 签发链路,你的 Web 应用才算把「进门安全」做扎实。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「tools」更多文章

  1. AI 网关与模型路由:多模型统一入口、fallback、限流与成本控制
  2. 密钥与环境配置:Vercel、Cloudflare 环境变量与密钥轮换实战
  3. 图片与媒体优化:Image CDN、AVIF-WebP 与响应式图片实战