unsafe-inline 在现代浏览器中常失效,因 CSP Level 2 起其仅在未指定 nonce/哈希时退化启用;替代方案是使用一次性 nonce,需头与标签值严格一致;strict-dynamic 可传递信任,但须与 nonce 共用。

内联脚本和样式在 CSP 中默认被禁用,想让它生效必须显式声明 unsafe-inline,但现代浏览器已普遍不买账——它在大多数场景下根本不起作用。
为什么 unsafe-inline 在 Chrome/Firefox/Safari 里经常失效
不是浏览器 bug,而是标准演进的结果:从 CSP Level 2 开始,script-src 和 style-src 中的 unsafe-inline 被限制为“仅当未指定哈希/nonce 时才退化启用”。一旦你写了 nonce-... 或 sha256-...,unsafe-inline 就被完全忽略。
- Chrome 70+、Firefox 65+、Safari 15.4+ 均按此逻辑执行
- 即使你写成
script-src 'unsafe-inline' 'nonce-abc',unsafe-inline也无效 - 只有在完全没提供任何哈希或 nonce 的情况下,
unsafe-inline才可能回退生效(且仅限于旧版兼容模式)
替代方案:用 nonce 代替 unsafe-inline 的实操要点
服务端每次响应需生成唯一 nonce 值,并同步注入到 HTTP 头和 HTML 中。关键不是“加不加”,而是“怎么加才不翻车”:
- HTTP 响应头中必须写完整:
Content-Security-Policy: script-src 'nonce-rAnd0mBase64==' - 对应 HTML 中的
<script nonce="rAnd0mBase64==">...</script>,值必须严格一致(含等号、大小写) - 不能复用 nonce——每个请求都要新生成,否则会触发浏览器拒绝执行
- 注意 Base64 编码后末尾的
=可能被某些模板引擎自动过滤,要验证最终输出是否完整
兼容老浏览器的 fallback 策略怎么写才不冲突
想兼顾 IE11 或旧安卓 WebView?别用双策略头,CSP 不支持 fallback 头。正确做法是只发一个头,靠语法降级:
- 不要同时发
Content-Security-Policy和X-Content-Security-Policy,后者在现代浏览器会被忽略,但可能干扰解析 - 若必须支持 IE10/IE11,只能用
X-Content-Security-Policy单独头,且只支持script-src和object-src等极小子集 - 更现实的方案是放弃对无 nonce 支持的老环境开放内联,改用外链 + 预加载,或用
strict-dynamic启动信任链
strict-dynamic 是什么,什么时候该用它
它是 CSP Level 3 引入的信任传递机制,不依赖域名或哈希,而是“首个带 nonce 的脚本所加载的后续脚本自动可信”。适合动态加载模块的场景(如 webpack + runtime):
- 写法示例:
script-src 'nonce-abc' 'strict-dynamic' - 必须和
nonce(或hash)共存,单独写'strict-dynamic'无效 - 不兼容 IE、旧 Safari(
- 注意:它会忽略
https:等传统源白名单,所以别混写'strict-dynamic' https:,后者会被直接丢弃
真正卡住人的往往不是“要不要加 nonce”,而是生成逻辑嵌在模板里却忘了随请求刷新,或者 HTTP 头和 HTML 标签里的值看似一样、实则空格或编码不一致。浏览器不会报错,只会静默屏蔽——得靠 DevTools 的 Console 里那条 “Refused to execute inline script…” 提示反向排查。











