配置 Content-Security-Policy (CSP) 头部是防止跨站脚本攻击 (XSS) 的有效手段,最推荐的做法是先在“报告模式”下运行收集策略,确认无误后再开启“强制模式”。
先说结论:直接开启强制策略容易导致网站功能异常,建议采用渐进式方案,先观察再拦截。
- 先判断:梳理站内所有脚本来源,包括第三方 CDN 和内联脚本。
- 优先做:配置 Content-Security-Policy-Report-Only 头进行灰度测试。
- 再验证:通过浏览器控制台确认无报错后,切换为正式策略。
命令速用版
根据你的 Web 服务器类型,在响应头中添加 CSP 配置。以下是常见服务器的配置片段,初始阶段请使用 Report-Only 模式:
# Nginx 配置示例 (报告模式)
add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' 'unsafe-inline'; report-uri /csp-report; report-to \"csp-endpoint\"" always;# Apache 配置示例 (报告模式)
Header set Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' 'unsafe-inline'; report-uri /csp-report; report-to \"csp-endpoint\""注意:上述示例为了兼容性暂时保留了 'unsafe-inline',正式环境请替换为 nonce 或 hash。
生产环境策略模板
实际生产环境通常需要更细致的指令,以下是一个较为完整的策略模板,可根据实际情况裁剪:
default-src 'self';
script-src 'self' https://cdn.example.com 'nonce-{{random_nonce}}';
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https://*.example.com;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
report-uri /csp-report;
report-to "csp-endpoint"说明:script-src 中使用了 nonce 机制,style-src 暂时保留 unsafe-inline 以防样式错乱,建议后续逐步优化。
Nonce 动态生成实现
为了避免使用 unsafe-inline,建议为内联脚本生成随机 nonce。以下是一个 Node.js Express 中间件示例:
app.use((req, res, next) => {
const nonce = Buffer.from(Math.random().toString(36)).toString('base64');
res.locals.nonce = nonce;
res.setHeader('Content-Security-Policy', `script-src 'self' 'nonce-${nonce}'`);
next();
});在 HTML 模板中引用:
<script nonce="<%= nonce %>">
console.log('安全脚本执行');
</script>分步处理
第一步:资产梳理
检查页面引用的所有外部资源,包括 JavaScript、CSS、图片、字体以及 WebSocket 连接。特别注意第三方统计代码、广告联盟和 CDN 资源。
第二步:开启报告模式
在服务器配置中启用 Report-Only 模式。此模式下,浏览器会拦截违规资源并发送报告,但不会实际阻断页面展示,适合生产环境观察。注意同时配置 Report-To 头以支持新标准。
第三步:收集与分析报告
搭建接收报告的后端接口(如 /csp-report),或使用浏览器控制台手动查看违规记录。根据报告内容调整白名单,将必要的合法来源加入策略。
第四步:切换强制模式
当报告模式运行一段时间且无异常违规后,将 header 名改回 Content-Security-Policy,正式开启拦截。
怎么验证是否生效
打开浏览器开发者工具(通常按 F12),切换到“控制台”标签页。刷新页面后,如果 CSP 生效且存在违规资源,控制台会打印红色的 CSP 错误信息,例如" Refused to load the script... "。
在“网络”标签页中,被 CSP 拦截的请求状态通常显示为 (blocked:csp) 或类似标识。
常见坑
1. 内联脚本被误杀
默认策略不允许<script>标签内直接写代码。如果需要保留,建议使用 nonce 或 hash 机制,而不是直接添加 unsafe-inline,后者会大幅降低安全性。
2. 第三方服务变更
引用的第三方 CDN 域名可能会变动,导致突然生效的 CSP 策略阻断业务。建议锁定具体子域名或定期复查。
3. 重定向丢失
部分服务器配置在发生 301/302 重定向时可能丢失自定义 header,需在配置中确保所有响应路径都携带 CSP 头。
4. report-to 兼容性
report-to 是较新的标准,旧版浏览器可能不支持。建议同时保留 report-uri 以确保报告能正常发送。
参考来源
- MDN Web Docs: Content-Security-Policy - https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP
- OWASP: Content Security Policy Cheat Sheet - https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html