nonce是浏览器放行内联脚本的唯一可信凭证;动态脚本默认仍被拦截,除非csp同时配置'strict-dynamic'与匹配的'nonce-xxx',且首个脚本真实加载并手动为后续动态创建的script设置相同nonce值。

nonce 不是“辅助”CSP 的可选开关,它是浏览器放行内联脚本的**唯一可信凭证**;动态脚本(document.createElement('script')、eval、innerHTML 插入)默认仍被拦截,除非 CSP 明确启用 'strict-dynamic' 且首个执行脚本已携带匹配的 nonce。
为什么加了 nonce 还报 Refused to execute inline script
90% 的情况不是标签写错了,而是两端没对齐:
- 响应头里漏了
Content-Security-Policy: script-src 'nonce-xxx',或写了'nonce-XXX'(大小写不一致) - HTML 源码中
<script nonce="xxx"></script>的值被模板引擎转义截断,比如 Jinja2 里{{ nonce }}没加|safe,导致引号或=被 HTML 编码 - 用了
<meta http-equiv="Content-Security-Policy">—— 该方式**完全不支持nonce**,必须走 HTTP 响应头 - 服务端生成的 nonce 含
+、/、=(如 Base64 编码未清洗),导致响应头解析失败
strict-dynamic 和 nonce 必须共存才管用
只配 'nonce-abc123',后续 JS 动态创建的脚本照样被拦。真正起效的组合是:
- 响应头必须同时包含两者:
script-src 'nonce-abc123' 'strict-dynamic'(顺序无关,但缺一不可) - 首个执行的
<script nonce="abc123"></script>必须存在、非空、且在 DOM 中真实加载(不能是占位空标签) - 后续用
document.createElement('script')创建的脚本,必须手动设script.nonce = 'abc123',值必须与初始nonce字符级一致 -
'strict-dynamic'对onclick等内联事件无效——这类仍需改用addEventListener
Webpack/Vite/HTMX 等构建或框架场景下,nonce 容易丢在哪
你手动写的 <script nonce="xxx"></script> 能过,但框架自动注入的脚本常被静默拦截:
- Webpack:runtime 启动逻辑(
__webpack_require__)默认不带nonce,需在入口<script></script>标签最顶部声明window.__webpack_nonce__ = 'xxx'(必须在__webpack_require__执行前) - Vite:不能只靠
index.html模板插值,需在vite.config.ts中配置build.rollupOptions.output.manualChunks,并确保 HMR 初始化脚本由模板渲染注入、显式带nonce - HTMX:其
evalScript函数会丢掉原始script.nonce,需手动补:newScript.nonce = script.nonce - React/Vue SSR:hydration 不会覆盖服务端已注入的
nonce,但dangerouslySetInnerHTML或v-html插入的脚本不会继承它,必须显式传入当前请求的值
最常被忽略的一点:nonce 值必须由服务端每次响应动态生成,且在 HTML 解析完成前就写进标签——JS 后续补 setAttribute('nonce', ...) 完全无效,浏览器早已跳过校验阶段。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











