土法炼钢兴趣小组的算法知识备份

【网络工程】HTTP 安全头完整实战:CORS、CSP、HSTS

文章导航

分类入口
network
标签入口
#http-security#cors#csp#hsts#security-headers

目录

HTTP 安全头是 Web 应用安全的第一道防线。它们通过 HTTP 响应头指示浏览器执行特定的安全策略——限制跨域请求、防止 XSS 注入、强制 HTTPS、禁止点击劫持。一个正确的安全头配置可以大幅降低攻击面,但配置不当也可能导致功能异常或给用户带来安全风险。

本文从工程师视角系统剖析每一个重要的安全头,覆盖从配置到调试到渐进部署的完整实践。

一、CORS:跨源资源共享

1.1 同源策略与 CORS 的关系

同源策略(Same-Origin Policy)是浏览器的基本安全模型。它限制了一个源(协议 + 域名 + 端口)的文档或脚本与另一个源的资源交互。

同源判定示例(基于 https://api.example.com:443):

https://api.example.com/path    → ✅ 同源
http://api.example.com/path     → ❌ 协议不同(https vs http)
https://www.example.com/path    → ❌ 域名不同(api vs www)
https://api.example.com:8443    → ❌ 端口不同(443 vs 8443)

CORS(Cross-Origin Resource Sharing,跨源资源共享)是一种机制,允许服务端声明”哪些源可以访问我的资源”。

1.2 简单请求 vs 预检请求

CORS 将请求分为两类:

简单请求(Simple Request)— 直接发送,不触发预检:
条件:
  1. 方法: GET / HEAD / POST
  2. 头字段: 仅限 Accept, Accept-Language, Content-Language,
     Content-Type (限 text/plain, multipart/form-data, application/x-www-form-urlencoded)
  3. 没有 ReadableStream body
  4. 没有事件监听器

预检请求(Preflight Request)— 先发 OPTIONS,再发实际请求:
触发条件(任一):
  1. 方法: PUT / DELETE / PATCH 等
  2. 头字段: Authorization, Content-Type: application/json 等
  3. 自定义头: X-Custom-Header

1.3 预检请求的完整流程

# 步骤 1: 浏览器自动发送 OPTIONS 预检请求
OPTIONS /api/users HTTP/2
Host: api.example.com
Origin: https://www.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization

# 步骤 2: 服务端返回 CORS 策略
HTTP/2 204 No Content
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
# 预检结果缓存 24 小时,期间不再发送 OPTIONS

# 步骤 3: 浏览器发送实际请求
POST /api/users HTTP/2
Host: api.example.com
Origin: https://www.example.com
Content-Type: application/json
Authorization: Bearer eyJhbG...

# 步骤 4: 服务端响应(必须包含 CORS 头)
HTTP/2 201 Created
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Expose-Headers: X-Request-Id
# 如果响应中没有 Access-Control-Allow-Origin,浏览器会丢弃响应

1.4 CORS 头字段完整指南

# ── 响应头字段 ──

# 允许的源(必须精确匹配,或使用 *)
Access-Control-Allow-Origin: https://www.example.com
# 或
Access-Control-Allow-Origin: *
# 注意:使用 * 时不能携带 Cookie(credentials)

# 允许的 HTTP 方法
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH

# 允许的请求头
Access-Control-Allow-Headers: Content-Type, Authorization, X-Request-Id

# 允许携带凭证(Cookie、Authorization 头)
Access-Control-Allow-Credentials: true
# 注意:使用 Credentials 时,Allow-Origin 不能是 *

# 暴露给 JavaScript 的响应头(默认只暴露 Cache-Control 等安全头)
Access-Control-Expose-Headers: X-Request-Id, X-RateLimit-Remaining

# 预检结果缓存时间
Access-Control-Max-Age: 86400
# Chrome 最大 7200 秒(2 小时),Firefox 最大 86400 秒

1.5 Nginx CORS 配置

# /etc/nginx/conf.d/cors.conf

map $http_origin $cors_origin {
    default "";
    "~^https://www\.example\.com$" $http_origin;
    "~^https://app\.example\.com$" $http_origin;
    "~^https://admin\.example\.com$" $http_origin;
}

server {
    location /api/ {
        # 设置 CORS 头
        if ($cors_origin != "") {
            add_header Access-Control-Allow-Origin $cors_origin always;
            add_header Access-Control-Allow-Credentials true always;
            add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
            add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Request-Id" always;
            add_header Access-Control-Expose-Headers "X-Request-Id, X-RateLimit-Remaining" always;
            add_header Access-Control-Max-Age 7200 always;
        }

        # 处理 OPTIONS 预检
        if ($request_method = OPTIONS) {
            return 204;
        }

        proxy_pass http://backend;
    }
}

1.6 CORS 的性能影响

预检请求(OPTIONS)会增加一个额外的 RTT:

没有预检:
  [Request] ──→ [Response]      总计: 1 RTT

有预检:
  [OPTIONS] ──→ [204]           预检: 1 RTT
  [Request] ──→ [Response]      实际: 1 RTT
                                 总计: 2 RTT

缓解策略:
1. Access-Control-Max-Age: 7200(缓存预检结果 2 小时)
2. 尽可能使用简单请求(避免触发预检)
   - 使用 application/x-www-form-urlencoded 而非 application/json
   - 但这会牺牲 API 设计的清晰性
3. 同源部署(同域名下的 API 不需要 CORS)
   - /api/* → 后端服务
   - / → 前端静态资源

1.7 CORS 常见错误

❌ 错误 1: Access-Control-Allow-Origin: *  +  Credentials: true
   浏览器报错: "Cannot use wildcard with credentials"
   修复: 使用具体的 Origin 值

❌ 错误 2: 只在 200 响应中返回 CORS 头
   结果: 4xx/5xx 错误时浏览器无法读取错误信息
   修复: add_header ... always(Nginx 中 always 表示所有状态码)

❌ 错误 3: 遗漏 Vary: Origin
   结果: CDN 缓存了一个 Origin 的 CORS 头,返回给另一个 Origin
   修复: 动态 Origin 时必须添加 Vary: Origin

❌ 错误 4: 忘记处理 OPTIONS 方法
   结果: 预检请求被后端框架作为普通请求处理,返回 405
   修复: 在代理层返回 204,或在框架中注册 OPTIONS 路由

二、CSP:内容安全策略

2.1 CSP 基础

内容安全策略(Content Security Policy)通过白名单机制控制页面可以加载哪些资源:

# CSP 通过 HTTP 头或 <meta> 标签设置
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com

# 含义:
# default-src 'self': 默认只允许加载同源资源
# script-src 'self' https://cdn.example.com: 脚本可以从同源或 CDN 加载
# 其他未列出的资源类型继承 default-src 的规则

2.2 CSP 指令全表

# 资源加载指令
default-src    # 所有资源的默认策略
script-src     # JavaScript 脚本
style-src      # CSS 样式表
img-src        # 图片
font-src       # 字体文件
connect-src    # XHR/fetch/WebSocket 连接目标
media-src      # <video>/<audio> 媒体
object-src     # <object>/<embed>/<applet>(建议设为 'none')
frame-src      # <iframe> 嵌入的页面
child-src      # Web Worker 和嵌入的浏览上下文
worker-src     # Web Worker / Service Worker / Shared Worker
manifest-src   # Web App Manifest

# 文档指令
base-uri       # <base> 标签的 URL(建议 'self')
form-action    # <form> 的提交目标
frame-ancestors # 谁可以嵌入此页面(替代 X-Frame-Options)

# 导航指令
navigate-to    # 页面可以导航到的 URL

# 报告指令
report-uri     # CSP 违规报告的目标 URL(已废弃)
report-to      # CSP 违规报告的目标(使用 Reporting API)

2.3 CSP 的源列表语法

# 关键字值(必须带引号)
'self'             # 同源
'none'             # 禁止所有
'unsafe-inline'    # 允许内联脚本/样式(不推荐!)
'unsafe-eval'      # 允许 eval()(不推荐!)
'strict-dynamic'   # 信任由受信任脚本动态加载的脚本
'nonce-{random}'   # 允许特定 nonce 的内联脚本

# URL 匹配
https://cdn.example.com          # 精确域名
https://*.example.com            # 通配符子域名
https:                           # 所有 HTTPS URL
data:                            # data: URI
blob:                            # blob: URI

# 哈希值(允许特定内容的内联脚本/样式)
'sha256-{base64-hash}'

2.4 CSP 渐进部署

直接部署严格的 CSP 通常会导致功能异常。推荐的渐进路径:

# 阶段 1: Report-Only(只报告不阻止,观察几周)
Content-Security-Policy-Report-Only: 
    default-src 'self'; 
    script-src 'self' https://cdn.example.com;
    style-src 'self' 'unsafe-inline';
    img-src 'self' data: https:;
    report-to csp-endpoint

# 配置报告端点
Reporting-Endpoints: csp-endpoint="https://report.example.com/csp"

# 阶段 2: 分析报告,修复违规
# 常见违规:
# - 内联脚本被阻止 → 移到外部文件或使用 nonce
# - 第三方资源被阻止 → 加入白名单
# - eval() 被阻止 → 替换为安全的替代方案

# 阶段 3: 部署强制模式
Content-Security-Policy: 
    default-src 'self'; 
    script-src 'self' https://cdn.example.com;
    style-src 'self' 'unsafe-inline';
    img-src 'self' data: https:;

# 阶段 4: 进一步收紧
# 使用 nonce 替换 'unsafe-inline'
# 移除不必要的第三方域名

2.5 Nonce-based CSP

<!-- 服务端为每个请求生成唯一的 nonce -->
<!-- HTTP 头: Content-Security-Policy: script-src 'nonce-abc123random' -->

<!-- 带 nonce 的内联脚本会被执行 -->
<script nonce="abc123random">
    console.log('This script is allowed');
</script>

<!-- 没有 nonce 或 nonce 不匹配的内联脚本会被阻止 -->
<script>
    console.log('This script is BLOCKED by CSP');
</script>

<!-- XSS 注入的脚本也会被阻止(攻击者不知道 nonce) -->
<script>steal(document.cookie)</script>  <!-- BLOCKED -->
// Go: 生成 CSP nonce
package main

import (
    "crypto/rand"
    "encoding/base64"
    "fmt"
    "net/http"
)

func generateNonce() string {
    b := make([]byte, 16)
    rand.Read(b)
    return base64.StdEncoding.EncodeToString(b)
}

func handler(w http.ResponseWriter, r *http.Request) {
    nonce := generateNonce()
    csp := fmt.Sprintf("script-src 'nonce-%s' 'strict-dynamic'", nonce)
    w.Header().Set("Content-Security-Policy", csp)
    
    html := fmt.Sprintf(`<html>
<script nonce="%s">console.log("allowed");</script>
</html>`, nonce)
    w.Write([]byte(html))
}

2.6 strict-dynamic

strict-dynamic 是 CSP Level 3 引入的关键特性。它允许受信任的脚本动态加载其他脚本,而不需要预先白名单所有 CDN URL:

# 传统方式:需要列出所有第三方脚本域名
Content-Security-Policy: script-src 'self' 
    https://cdn.example.com 
    https://analytics.google.com 
    https://widget.intercom.io
    https://js.stripe.com
    ...
# 维护噩梦:每增加一个第三方脚本都要更新 CSP

# strict-dynamic 方式:
Content-Security-Policy: script-src 'nonce-{random}' 'strict-dynamic'
# 含义:
# 1. 只有带正确 nonce 的脚本可以执行
# 2. 这些受信任脚本动态创建的 <script> 也被允许执行
# 3. URL 白名单被忽略(只看 nonce)

三、HSTS:HTTP 严格传输安全

3.1 HSTS 基础

HSTS(HTTP Strict Transport Security)告诉浏览器”在指定时间内,只通过 HTTPS 访问这个域名”:

# 基本 HSTS
Strict-Transport-Security: max-age=31536000
# 1 年内,浏览器自动将 http:// 转为 https://
# 不发送 HTTP 请求(内部 307 重定向)

# 包含子域名
Strict-Transport-Security: max-age=31536000; includeSubDomains
# 所有子域名也强制 HTTPS

# HSTS Preload
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
# 申请加入浏览器内置的 HSTS 预加载列表

3.2 HSTS 解决的问题

没有 HSTS 时的攻击场景(SSL Stripping):

用户输入 example.com → 浏览器发送 HTTP 请求
     ↓
中间人拦截 HTTP 请求
     ↓
中间人与 example.com 建立 HTTPS 连接
中间人与用户保持 HTTP 连接
     ↓
用户在 HTTP 上操作,所有数据被中间人窃取

有 HSTS 时:
用户输入 example.com → 浏览器检查 HSTS 列表
     ↓
发现 example.com 在 HSTS 列表中
     ↓
内部 307 重定向到 https://example.com
     ↓
直接发送 HTTPS 请求,没有 HTTP 请求可以被拦截

3.3 HSTS Preload

HSTS Preload 将域名硬编码到浏览器中,解决”首次访问”的问题:

# 申请 HSTS Preload:https://hstspreload.org
# 要求:
# 1. 有效的 HTTPS 证书
# 2. 将所有 HTTP 流量重定向到 HTTPS
# 3. 所有子域名也支持 HTTPS
# 4. HSTS 头包含:max-age >= 31536000; includeSubDomains; preload

# 注意:加入 Preload 列表后很难移除!
# 移除过程可能需要数月(等待浏览器更新)
# 确保所有子域名都支持 HTTPS 后再申请

3.4 HSTS 的工程风险

风险 1: 子域名不支持 HTTPS
  设置了 includeSubDomains,但 internal.example.com 没有证书
  → 用户无法访问 internal.example.com
  修复: 确认所有子域名都有 HTTPS 证书后再启用

风险 2: max-age 过长导致回滚困难
  设置了 max-age=2年,后来需要回退到 HTTP
  → 已访问过的用户 2 年内无法通过 HTTP 访问
  建议: 从小的 max-age 开始(如 300 秒),逐步增加

风险 3: Preload 后无法回退
  加入 Preload 列表后,全球所有浏览器都会强制 HTTPS
  即使从服务端移除 HSTS 头,Preload 列表中的条目仍然生效

四、X-Frame-Options 与 frame-ancestors

4.1 点击劫持防御

# X-Frame-Options(旧标准,仍然广泛使用)
X-Frame-Options: DENY              # 完全禁止被嵌入 iframe
X-Frame-Options: SAMEORIGIN        # 只允许同源嵌入

# CSP frame-ancestors(新标准,更灵活)
Content-Security-Policy: frame-ancestors 'self'                  # 同源
Content-Security-Policy: frame-ancestors 'self' https://trusted.com  # 同源 + 白名单
Content-Security-Policy: frame-ancestors 'none'                  # 禁止嵌入

# 推荐同时设置(向后兼容):
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self'

4.2 点击劫持攻击原理

<!-- 攻击页面:将目标网站嵌入透明 iframe -->
<style>
    iframe {
        position: absolute;
        opacity: 0;           /* 透明,用户看不到 */
        width: 500px;
        height: 500px;
        z-index: 10;          /* 在按钮上方 */
    }
    .fake-button {
        position: absolute;
        /* 位置对齐到 iframe 中的"删除账户"按钮 */
    }
</style>
<iframe src="https://target.com/settings"></iframe>
<button class="fake-button">点击领取奖品</button>
<!-- 用户以为在点"领取奖品",实际在点 target.com 的"删除账户" -->

五、其他重要安全头

5.1 Referrer-Policy

# 控制 Referer 头泄露多少信息
Referrer-Policy: strict-origin-when-cross-origin
# 同源请求: 完整 URL
# 跨源请求 (HTTPS→HTTPS): 只发送 Origin
# 跨源请求 (HTTPS→HTTP): 不发送 Referer

# 可选值:
Referrer-Policy: no-referrer                     # 从不发送
Referrer-Policy: no-referrer-when-downgrade      # HTTPS→HTTP 不发送(默认)
Referrer-Policy: origin                          # 只发送 Origin
Referrer-Policy: origin-when-cross-origin        # 跨域只发 Origin,同域发完整 URL
Referrer-Policy: same-origin                     # 只对同源请求发送
Referrer-Policy: strict-origin                   # 只发 Origin,降级不发
Referrer-Policy: strict-origin-when-cross-origin # 推荐值
Referrer-Policy: unsafe-url                      # 始终发送完整 URL(不推荐)

5.2 Permissions-Policy

# 控制浏览器功能的使用(替代已废弃的 Feature-Policy)
Permissions-Policy: 
    camera=(),                    # 禁用摄像头
    microphone=(),                # 禁用麦克风
    geolocation=(self),           # 只允许同源使用地理位置
    payment=(self "https://pay.example.com"),  # 支付 API 白名单
    autoplay=(self),              # 自动播放限制
    fullscreen=(self)             # 全屏限制

# 禁用不需要的危险功能可以减少攻击面
# 即使 XSS 攻击者注入了使用这些功能的代码,浏览器也会拒绝

5.3 X-Content-Type-Options

# 防止 MIME 类型嗅探
X-Content-Type-Options: nosniff

# 问题场景:
# 攻击者上传一个名为 image.jpg 的文件,内容实际上是 JavaScript
# 没有 nosniff: 浏览器可能嗅探内容类型,将其作为 JS 执行
# 有 nosniff: 浏览器严格按照 Content-Type 处理,拒绝执行

# 这个头应该始终设置

5.4 X-DNS-Prefetch-Control

# 控制 DNS 预取行为
X-DNS-Prefetch-Control: off
# 防止浏览器自动预取页面中链接的 DNS
# 隐私考虑:DNS 预取可能泄露用户浏览行为

5.5 Cross-Origin-Opener-Policy(COOP)

# 控制窗口之间的跨源隔离
Cross-Origin-Opener-Policy: same-origin
# 可选值:
#   unsafe-none      — 默认行为,允许跨源窗口交互
#   same-origin-allow-popups — 同源,但允许弹出窗口
#   same-origin      — 完全隔离,跨源窗口互相不可引用

COOP 的核心作用是防止 Spectre 类侧信道攻击。当设置 same-origin 时,跨源弹出窗口无法获取 window.opener 引用,攻击者无法通过共享的浏览上下文(browsing context group)读取敏感数据。

5.6 Cross-Origin-Embedder-Policy(COEP)

# 要求所有子资源都明确允许被嵌入
Cross-Origin-Embedder-Policy: require-corp
# 可选值:
#   unsafe-none  — 默认行为
#   require-corp — 所有跨源资源必须设置 CORP 头或 CORS 头
#   credentialless — 跨源请求不携带凭证(较新,部分浏览器支持)

COOP + COEP 联合使用可启用 crossOriginIsolated 状态,解锁高精度 API:

// 检查是否处于跨源隔离状态
if (self.crossOriginIsolated) {
    // 可使用 SharedArrayBuffer、高精度 performance.now()
    console.log("Cross-origin isolated: SharedArrayBuffer available");
}

5.7 安全头对照表

头字段                        │ 防御目标              │ 推荐值
──────────────────────────────┼───────────────────────┼──────────────────────────
Strict-Transport-Security     │ SSL 剥离              │ max-age=31536000; includeSubDomains
Content-Security-Policy       │ XSS/数据注入          │ default-src 'self'; script-src 'nonce-…'
X-Frame-Options               │ 点击劫持              │ SAMEORIGIN
X-Content-Type-Options        │ MIME 嗅探             │ nosniff
Referrer-Policy               │ 信息泄露              │ strict-origin-when-cross-origin
Permissions-Policy            │ 功能滥用              │ camera=(), microphone=()
Cross-Origin-Opener-Policy    │ Spectre 侧信道        │ same-origin
Cross-Origin-Embedder-Policy  │ 跨源数据泄露          │ require-corp

六、安全头的完整配置模板

6.1 Nginx 安全头配置

# /etc/nginx/conf.d/security-headers.conf

# HSTS(确认所有子域名支持 HTTPS 后启用 includeSubDomains)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

# CSP(根据实际需求调整)
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.example.com; frame-ancestors 'self'; base-uri 'self'; form-action 'self'" always;

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

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

# Referrer 策略
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# 权限策略
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(self)" always;

6.2 安全头审计

#!/bin/bash
# security_headers_audit.sh - HTTP 安全头审计脚本

URL="${1:-https://example.com}"
echo "=== Security Headers Audit: $URL ==="
echo ""

headers=$(curl -sI "$URL")

check_header() {
    local name="$1"
    local value=$(echo "$headers" | grep -i "^${name}:" | head -1)
    if [ -n "$value" ]; then
        echo "✅ $name: $(echo "$value" | cut -d: -f2- | xargs)"
    else
        echo "❌ $name: Missing"
    fi
}

check_header "Strict-Transport-Security"
check_header "Content-Security-Policy"
check_header "X-Frame-Options"
check_header "X-Content-Type-Options"
check_header "Referrer-Policy"
check_header "Permissions-Policy"

echo ""
echo "--- Additional Checks ---"

# 检查是否有不安全的头
if echo "$headers" | grep -qi "X-Powered-By"; then
    echo "⚠️  X-Powered-By header present (information disclosure)"
fi
if echo "$headers" | grep -qi "Server:"; then
    server=$(echo "$headers" | grep -i "^Server:" | head -1)
    echo "⚠️  $server (consider hiding version)"
fi
# 使用在线工具检查
# securityheaders.com
# observatory.mozilla.org
curl -s "https://securityheaders.com/?q=https://example.com&followRedirects=on" | \
    grep -o 'class="[A-F]"' | head -1

七、安全头部署的常见陷阱

7.1 CSP 破坏了内联脚本

问题: 部署 CSP 后,Google Analytics、第三方聊天组件等内联脚本停止工作

解决方案(按优先级):
1. 将内联脚本移到外部文件 → script-src 'self'
2. 使用 nonce → script-src 'nonce-{random}'
3. 使用哈希 → script-src 'sha256-{hash}'
4. 最后手段: 'unsafe-inline'(不推荐,但比没有 CSP 好)

7.2 HSTS 影响开发环境

问题: 在开发环境设置了 HSTS,但开发环境使用自签名证书或 HTTP
结果: 浏览器拒绝通过 HTTP 访问开发环境

解决方案:
1. 不在开发环境设置 HSTS
2. 使用 mkcert 生成本地信任的开发证书
3. Chrome: chrome://net-internals/#hsts → 删除域名的 HSTS 记录

7.3 CORS + 缓存不一致

问题: CDN 缓存了一个 Origin 的 CORS 响应头,返回给另一个 Origin

场景:
1. www.example.com 请求 → CDN 缓存 Access-Control-Allow-Origin: https://www.example.com
2. app.example.com 请求 → CDN 返回缓存的 Access-Control-Allow-Origin: https://www.example.com
3. 浏览器拒绝: Origin https://app.example.com 不匹配

解决方案:
  Vary: Origin
  告诉 CDN 按 Origin 头值区分缓存

7.4 Permissions-Policy 语法变更

问题: Feature-Policy 已废弃,旧配置不生效

旧语法(Feature-Policy):
  Feature-Policy: camera 'none'; microphone 'none'

新语法(Permissions-Policy):
  Permissions-Policy: camera=(), microphone=()

差异:
  - 分隔符从分号变为逗号
  - 值用括号 () 而非引号 'none'
  - self 不加引号: geolocation=(self)
  - 指定源: payment=(self "https://pay.example.com")

两个头可以同时设置以覆盖旧浏览器

7.5 安全头与 SPA 框架的冲突

问题: React/Vue 等 SPA 框架的开发模式依赖 eval() 和内联脚本

常见报错:
  Refused to evaluate a string as JavaScript because 'unsafe-eval'
  is not an allowed source of script in CSP

开发环境解决方案:
  不在开发环境启用严格 CSP
  或仅在生产构建时注入 CSP 头

生产环境方案:
  - 避免使用依赖 eval() 的库(如部分模板引擎)
  - 使用 nonce + strict-dynamic 而不是 'unsafe-eval'
  - Webpack: 设置 devtool: 'source-map' 而非 'eval-source-map'
  - Vue: 使用运行时构建(不需要模板编译器)

7.6 多层代理的安全头覆盖

问题: CDN / 反向代理 / 应用三层都设置了安全头,最终结果不可预测

典型场景:
  应用层: Content-Security-Policy: default-src 'self'
  Nginx:  add_header Content-Security-Policy "default-src 'self' https://cdn.com"
  CDN:    自动添加 X-Frame-Options: SAMEORIGIN

结果: 可能出现重复头(两个 CSP 头),浏览器行为取决于实现

解决方案:
  1. 明确安全头的"唯一设置点"(通常是应用层或 Nginx,不要两者都设)
  2. Nginx 使用 proxy_hide_header 先移除上游头,再 add_header 设置
  3. 定期用 curl -I 验证最终响应头

八、总结

HTTP 安全头的部署是一个”渐进增强”的过程,不是一次性的开关。核心建议:

  1. HSTS 从小 max-age 开始(300 秒),确认没问题后逐步增加到 31536000 秒(1 年)。includeSubDomains 和 preload 需要格外谨慎。

  2. CSP 从 Report-Only 开始。先观察报告数据,修复违规项,再切换到强制模式。使用 nonce + strict-dynamic 是目前最佳的 CSP 策略。

  3. CORS 要精确配置。避免 Access-Control-Allow-Origin: * + Credentials 的组合。动态 Origin 时必须设置 Vary: Origin

  4. 安全头不是万能的。它们是”深度防御”的一层,不能替代输入验证、输出编码和安全的应用逻辑。XSS 的根本解决方案是不信任用户输入,安全头只是在应用防御失败时的兜底。

  5. 定期审计安全头配置。使用 securityheaders.com 或 Mozilla Observatory 检查你的配置。每次部署新的第三方脚本或更改域名结构后,都应该重新检查 CSP 和 CORS 配置。

  6. COOP + COEP 按需启用。如果你的应用需要 SharedArrayBuffer 或高精度计时器,配置 Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: require-corp,但要确保所有跨源资源都设置了正确的 CORS 或 CORP 头。

  7. 安全头的设置点要统一。避免在应用层、反向代理和 CDN 三个层面重复设置同一个安全头,选择一个层作为安全头的唯一控制点。


参考文献


上一篇:HTTP 压缩与传输编码:gzip、Brotli 与 zstd 工程选型

下一篇:HTTP 调试方法论:curl、DevTools 与 mitmproxy

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2025-08-04 · network

【网络工程】QUIC 生态与工程部署:从实验到生产

QUIC 已经不是实验性协议——HTTP/3 标准化后,CDN、浏览器和主流服务端框架都在推进 QUIC 支持。本文从工程视角对比主流 QUIC 库的成熟度和性能特征,讲解 CDN/负载均衡器的 QUIC 适配方案、从 TCP 迁移到 QUIC 的渐进路径、QUIC 调试工具链,以及生产环境的部署陷阱和性能调优实践。

2025-08-05 · network

【网络工程】eBPF 可编程网络:从包过滤到流量工程

eBPF 正在重新定义网络工程——从传统的 iptables/netfilter 规则堆砌,到可编程、可观测、高性能的网络数据平面。本文系统讲解 eBPF 网络程序类型(XDP/TC/Socket)、Map 数据结构、Cilium 的 eBPF 数据平面实现,以及 eBPF 在负载均衡、可观测性和网络安全中的工程实践。

2025-08-06 · network

【网络工程】可编程数据平面与 P4:软件定义转发

传统网络设备的转发逻辑固化在硬件中。P4 语言让交换机的转发管线可编程——你可以定义自己的包头解析、匹配规则和转发动作。本文从 P4 语言核心概念出发,讲解 Parser/Match-Action/Deparser 的编程模型、可编程交换机芯片(Tofino)的架构、P4 在数据中心和运营商网络中的应用案例,以及 P4 与 eBPF 的定位差异。


By .