正在加载,请稍候…

HTTP 安全标头完整指南:CSP、HSTS、CORS 等

掌握 HTTP 安全标头:Content-Security-Policy、HSTS、X-Frame-Options、CORS 配置、Permissions-Po

HTTP 安全标头完整指南:CSP、HSTS、CORS 等

安全标头:您的最后一道防线

安全标头并不能防止代码中的漏洞——它们限制了攻击者在漏洞存在时所能做的事情。它们是浏览器级别的安全网,能够捕获穿透应用程序防御的攻击。

好消息是:大多数安全标头只需一行代码即可实现。挑战在于理解每个标头的作用、哪种值适合您的应用程序,以及如何测试它们是否真正生效。

HTTP 安全标头完整指南:CSP、HSTS、CORS 等 插图

Content-Security-Policy (CSP)

最强大且最复杂的安全标头。CSP 为浏览器可以加载的每种资源类型定义了一个白名单:

Content-Security-Policy: 
  default-src 'self';
  script-src 'self' https://cdn.example.com 'nonce-{random}';
  style-src 'self' https://fonts.googleapis.com 'unsafe-inline';
  font-src 'self' https://fonts.gstatic.com;
  img-src 'self' data: https:;
  connect-src 'self' https://api.example.com wss://ws.example.com;
  media-src 'none';
  object-src 'none';
  frame-src https://youtube.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  upgrade-insecure-requests;
  report-uri https://example.com/csp-reports;

关键指令说明:

指令 控制内容
default-src 所有未指定资源类型的回退
script-src JavaScript 来源(最重要!)
style-src CSS 来源
img-src 图片来源
connect-src fetch、XHR、WebSocket 目标
frame-ancestors 谁可以嵌入此页面(替代 X-Frame-Options)
base-uri 限制 <base> 标签(防止 base 标签注入)
form-action 表单可以提交到哪里
upgrade-insecure-requests 将 HTTP 子资源升级为 HTTPS

从仅报告模式开始:

Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-reports

这会在不阻止任何内容的情况下报告违规——对于在部署前测试 CSP 至关重要。

CSP Nonces(优于 unsafe-inline)

// Express 中间件,每个请求生成新的 nonce
import crypto from 'crypto'

export function cspMiddleware(req: Request, res: Response, next: NextFunction) {
  const nonce = crypto.randomBytes(16).toString('base64')
  res.locals.nonce = nonce
  
  res.setHeader('Content-Security-Policy', [
    "default-src 'self'",
    `script-src 'self' 'nonce-${nonce}'`,
    `style-src 'self' 'nonce-${nonce}'`,
    "img-src 'self' data: https:",
    "object-src 'none'",
    "base-uri 'self'",
    "frame-ancestors 'none'",
  ].join('; '))
  
  next()
}

// 在模板中:
// <script nonce="<%= nonce %>">...</script>

HTTP Strict Transport Security (HSTS)

强制浏览器始终使用 HTTPS 访问您的域名:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  • max-age=31536000:浏览器记住仅 HTTPS 持续 1 年
  • includeSubDomains:应用于所有子域名
  • preload:允许提交到浏览器预加载列表(硬编码 HTTPS)

部署策略——从保守开始,逐步增加:

# 阶段 1:使用短 max-age 测试
Strict-Transport-Security: max-age=300

# 阶段 2:增加到 1 天
Strict-Transport-Security: max-age=86400

# 阶段 3:添加子域名(仅当所有子域名都支持 HTTPS 时)
Strict-Transport-Security: max-age=86400; includeSubDomains

# 阶段 4:全年(提交到预加载列表)
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

警告:带有 preload 的 HSTS 很难撤销。如果您无法从子域名提供 HTTPS,请不要使用 includeSubDomains

HTTP 安全标头完整指南:CSP、HSTS、CORS 等 插图

CORS(跨域资源共享)

CORS 控制哪些域名可以从浏览器向您的 API 发出请求:

// Express CORS 中间件
import cors from 'cors'

// ❌ 对于生产 API 过于宽松:
app.use(cors()) // 允许所有来源

// ✅ 显式白名单:
const allowedOrigins = [
  'https://app.example.com',
  'https://admin.example.com',
  process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : null,
].filter(Boolean) as string[]

app.use(cors({
  origin: (origin, callback) => {
    // 允许没有 origin 的请求(curl、Postman、服务器到服务器)
    if (!origin) return callback(null, true)
    
    if (allowedOrigins.includes(origin)) {
      callback(null, true)
    } else {
      callback(new Error(`CORS blocked: ${origin} not allowed`))
    }
  },
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization', 'X-CSRF-Token'],
  credentials: true,      // 允许 Cookie
  maxAge: 86400,          // 缓存预检请求 24 小时
}))

理解预检请求:

对于“复杂”请求(非简单方法、自定义标头):

浏览器首先发送 OPTIONS 请求:
OPTIONS /api/users HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization

服务器响应:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: POST, GET, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

然后浏览器发送实际请求。

X-Frame-Options 与 frame-ancestors

# 旧方式——IE11 仍然需要:
X-Frame-Options: DENY
# 或:
X-Frame-Options: SAMEORIGIN

# 现代方式(CSP frame-ancestors):
Content-Security-Policy: frame-ancestors 'none'
# 或:
Content-Security-Policy: frame-ancestors 'self' https://trusted-dashboard.example.com

为了最大浏览器兼容性,同时使用两者:

X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'

Permissions-Policy

控制页面和 iframe 的浏览器功能:

Permissions-Policy: 
  camera=(),
  microphone=(),
  geolocation=(self),
  payment=(self "https://payment.example.com"),
  usb=(),
  accelerometer=(),
  gyroscope=()
  • camera=():完全禁用(空 = 拒绝所有)
  • geolocation=(self):仅允许此来源
  • payment=(self "https://..."):允许此来源 + 特定第三方

HTTP 安全标头完整指南:CSP、HSTS、CORS 等 插图

其他安全标头

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

# 控制 Referrer 信息
Referrer-Policy: strict-origin-when-cross-origin
# strict-origin-when-cross-origin:跨域 HTTPS→HTTPS 仅发送来源,
# 同域发送完整 URL,HTTPS→HTTP 不发送

# 跨域策略(高级)
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin
# 这三个一起启用 SharedArrayBuffer 和高精度计时器
# 需要:WebAssembly 线程、Atomics、performance.measureUserAgentSpecificMemory()

完整标头配置

Express.js 配合 Helmet:

import helmet from 'helmet'

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", (req, res) => `'nonce-${res.locals.nonce}'`],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", "data:", "https:"],
      connectSrc: ["'self'", "https://api.example.com"],
      frameSrc: ["'none'"],
      objectSrc: ["'none'"],
      upgradeInsecureRequests: [],
    },
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true,
  },
  referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
  permittedCrossDomainPolicies: false,
  crossOriginEmbedderPolicy: false, // 仅在需要时启用
}))

Next.js(next.config.js):

const securityHeaders = [
  { key: 'X-DNS-Prefetch-Control', value: 'on' },
  { key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains; preload' },
  { key: 'X-Content-Type-Options', value: 'nosniff' },
  { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
  { key: 'X-Frame-Options', value: 'DENY' },
  { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=(self)' },
]

module.exports = {
  async headers() {
    return [
      {
        source: '/:path*',
        headers: securityHeaders,
      },
    ]
  },
}

测试安全标头

# 使用 curl 检查标头:
curl -I https://example.com

# 或检查特定标头:
curl -sI https://example.com | grep -i "content-security-policy"

# 在线工具:
# https://securityheaders.com — 为您的标头评分
# https://observatory.mozilla.org — 全面安全分析
# https://csp-evaluator.withgoogle.com — CSP 特定评估

# 使用仅报告 + 日志在本地测试 CSP:
# 设置:Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
# 添加路由记录违规:
app.post('/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => {
  console.log('CSP Violation:', JSON.stringify(req.body, null, 2))
  res.status(204).send()
})

安全标头添加成本低廉,并能有效减少攻击面。实施它们所需的成本——几行配置——远低于处理一次成功的 XSS 攻击,而适当的 CSP 本可以阻止它。

→ 使用 HTTP 状态码 参考查找 HTTP 状态码及其含义。