
把静态资源切到 CDN 域名后,页面能打开,按钮却没有反应;Network 面板里文件可能是 200,Console 则提示违反 Content Security Policy。此时继续刷新 CDN,未必能让脚本执行。要先核对浏览器收到的页面策略,以及实际被拦截的资源。
CSP 是浏览器执行的内容安全策略。CDN 可以改变响应头、资源地址或缓存结果,但最终是否允许脚本运行,由页面上的有效策略决定。本文面向管理网站与 CDN 配置的人员,示例均需替换域名与参数,不代表已在生产环境实测。
从 Console 找到具体指令
保留一次失败页面的 Console 报错和 Network 请求。重点记录:被阻止的 URL、报错引用的指令,以及页面收到的 Content-Security-Policy 响应头。不要只看脚本文件自己的响应头,页面文档上的策略才是这里的主要检查对象。
常见情况如下:
| 报错线索 | 优先检查 |
|---|---|
| 外部脚本域名不在允许范围 | script-src-elem、script-src 与实际资源域名 |
| 内联脚本无法执行 | nonce、hash 与内联内容 |
| 事件属性被阻止 | onclick 等内联事件与 script-src-attr |
| 动态执行代码被阻止 | eval() 等调用与构建产物 |
| 脚本执行了,接口仍失败 | connect-src,以及接口自身的 CORS |
若配置了 script-src-elem,脚本元素会优先受它约束;缺少对应指令时,再按回退规则检查 script-src 或 default-src。控制台通常会指出实际使用的指令,按照这一条查比猜测配置更快。[1][2]
CSP 与 CORS 是不同检查。盲目给资源加 Access-Control-Allow-Origin: *,不会修复 CSP 的来源限制。脚本通过 CSP 后仍可能遇到 CORS、完整性校验或 MIME 类型问题,应继续根据具体报错定位。
先对比源站与 CDN 返回的页面头
对同一页面执行 GET 请求,保存头部和 HTML:
curl -sS -D edge-headers.txt -o edge.html https://www.example.com/ # 仅在拥有源站访问权限、源站允许该连接时执行 curl -sS --resolve www.example.com:443:203.0.113.10 \ -D origin-headers.txt -o origin.html https://www.example.com/
203.0.113.10 是文档示例地址。--resolve 指定连接 IP,同时保留 URL 主机名用于 Host、TLS SNI 和证书校验。源站仅允许 CDN 访问时,使用获准的诊断入口,勿为排查临时向公网开放源站,也不要用 -k 跳过验证。
检查两份头部是否含有 CSP、Report-Only 或重复的 CSP 头;再查看 HTML 内是否存在声明策略的 meta 元素,以及实际脚本 URL。检查 Nginx、应用、安全插件与 CDN 是否分别添加了策略。
多个强制执行的 CSP 策略会分别生效,新增一个更宽松的头不会替换已有的严格限制。meta 中的策略也可能继续限制页面。若源站只允许本站脚本,CDN 额外添加一条允许新域名的策略,原策略仍可能阻止它。[1]
不要把生产响应中的 Cookie、敏感 URL 或带业务数据的 HTML 原样放进公开工单。
资源换域名后,核对浏览器实际使用的来源
'self' 指页面自身的源。页面在 www.example.com,脚本迁移到 static.example.net 后,即使两个域名属于同一公司,也不属于同源。
下面是说明来源关系的示例,仅适用于需要加载外部脚本、且不依赖内联脚本的简单页面,不能直接替换复杂站点的完整策略:
Content-Security-Policy: default-src 'self'; script-src 'self' https://static.example.net; object-src 'none'; base-uri 'self'
应允许实际受控的精确来源,避免为了恢复功能直接放开所有 HTTPS 站点或通配所有域名。若资源经过重定向,检查完整跳转链与最终响应;若应用动态加载分包、分析脚本或其他依赖,也要用真实页面验证。
存在 script-src-elem 时,只修改 script-src 可能无效。使用 'strict-dynamic' 的策略还会改变支持它的浏览器对脚本信任传播和来源列表的处理;不要简单认定“已经加入域名,必然会执行”。这类站点应按现有 nonce/hash 信任模型调整,而非随手附加来源。[2]
nonce 必须与当前 HTML 配套
nonce 是服务器为响应生成的不可预测随机值。页面策略中的 nonce 与获准执行的 script 元素必须匹配:[1]
Content-Security-Policy: script-src 'nonce-REPLACE_PER_RESPONSE'; object-src 'none'; base-uri 'self'
<script nonce="REPLACE_PER_RESPONSE" src="https://static.example.net/app.js"></script>
示例中的占位符绝不能作为生产固定值。生产环境应使用密码学安全随机源,按每个响应生成新 nonce,并让模板与响应头使用同一个值。不要给所有脚本节点无差别补 nonce,这可能连同注入的恶意脚本一起授权。
CDN 缓存容易让这一机制出错:HTML 正文来自昨天的缓存,响应头却由另一层重新生成了 nonce;或者只更新了安全响应头,正文中的 script 仍携带旧值。即便两边使用同一个固定值、页面暂时恢复执行,也没有满足 nonce 应每次响应更新的要求。
需要 nonce 的动态页面,应明确由哪一层同时生成头部与正文。无法保证逐响应一致更新时,可考虑让这类 HTML 绕过共享缓存、静态指纹资源继续缓存;具体规则要与登录态和应用策略一起设计。不要假定 CDN 默认具备安全替换 HTML 与 nonce 的能力。
确实需要缓存静态 HTML 的站点,可评估基于 hash 的策略或移除内联脚本。hash 必须对应准确的脚本内容,空白或代码改变都可能导致校验失败;构建与发布过程需要同步更新策略。[1][2]
不要用 unsafe-inline 作为通用修复
添加 'unsafe-inline' 或 'unsafe-eval' 可能掩盖不兼容代码,也会改变安全边界。先明确阻止的究竟是内联脚本、事件属性还是动态代码执行。
例如,把 onclick 迁移为获准脚本中的 addEventListener,通常比为所有内联事件放开策略更容易控制。开发构建可能使用 eval 类工具生成代码,生产应检查构建配置,不能仅靠降低浏览器策略兜底。
nonce 对 script 元素的授权,不会自动授权所有内联事件属性。涉及 hash、unsafe-hashes 或第三方代码时,应逐项评估风险,保持策略尽可能小。[2]
用 Report-Only 观察候选策略,再小范围启用
修改前备份应用、Web 服务器和 CDN 现有配置。先通过响应头部署候选策略的 Content-Security-Policy-Report-Only,观察正常业务路径是否会违规,再逐步切换到强制执行。Report-Only 用于观察,不会放松已有的强制策略,也不会自动让当前被拦截的脚本恢复。[3]
Report-Only 不支持通过 meta 标签部署。需要收集报告时,应另行配置适用的报告机制,核对浏览器支持,并避免在报告中收集敏感查询参数或业务数据。只看一个首页没有报错,不足以说明支付、登录、管理端与延迟加载功能都兼容。
若修改 Nginx 配置,应先做配置测试,再按运维流程 reload;若修改 CDN 规则,先限定测试域名或小范围路径。回滚应恢复上一份已知可用且安全的配置,避免直接删除所有 CSP。
修复后同时验证安全策略与功能
用新浏览器会话和原有标签页分别检查:
页面最终收到的所有 CSP 都符合预期,没有遗留的重复头或 meta 限制。
脚本、动态分包和 API 请求正常,Console 不再出现对应违规。
nonce 与当前 HTML 配套,缓存路径不会重复使用固定值或混合不同版本。
登录、表单与其他关键流程正常,未为恢复功能放开无关来源。
如需刷新缓存,先刷新确认受影响的 HTML 对象,并核对其头部与正文是否一起更新。CDN 刷新不会清理用户浏览器或 Service Worker 已保存的旧页面,这些路径也要纳入复查。
一次有效的修复,应能指出被哪条策略阻止、策略由哪一层生成,以及修改后哪些业务路径已通过验证。保留这些记录,下一次迁移资源域名或升级构建工具时就能提前检查。
参考资料
资料核验日期:2026 年 10 月 21 日。示例需结合现有策略和浏览器支持调整。