nonce必须由服务端生成并严格匹配,常见错误包括客户端伪造、未清洗Base64字符、复用或硬编码、响应头与标签值不一致、框架内联脚本缺失nonce、未启用strict-dynamic导致动态脚本被拦截。

nonce不是前端能碰的变量,必须服务端每次生成
加了 nonce 属性却报 Refused to execute inline script,90% 是因为前端试图自己拼字符串。浏览器根本不认 Math.random()、Date.now() 或任何客户端生成的值——它只信任服务端在本次 HTTP 响应中注入的、加密安全的随机串。
常见错误包括:
-
crypto.randomBytes(16).toString('base64')未清洗,结果含+、/、=,导致响应头解析失败 - 复用同一 nonce(比如 SSR 模板被 CDN 缓存,或 Express 中间件漏了
res.locals.nonce = ...) - 把 nonce 硬编码进构建产物(如 Vite 的
index.html静态模板),失去“一次性”语义
响应头和 script 标签的值必须字符级完全一致
浏览器校验极其严格:大小写、空格、引号、换行,任一差异都会静默失败——不报错、不警告,脚本直接不执行。
排查时优先看两处:
立即学习“前端免费学习笔记(深入)”;
- 用
curl -I或 DevTools Network → Headers 查响应头:Content-Security-Policy: script-src 'nonce-abc123'(注意单引号、无空格、无多余字符) - 查看页面「源代码」(不是 Elements 面板),搜索
<script nonce=,确认属性值与响应头中'nonce-xxx'的xxx部分**逐字相同** - 特别注意模板插值是否转义失败:Jinja2 里
{{ nonce|safe }}漏了|safe,EJS 里<%= nonce %>被 HTML 转义成',导致实际值为空
框架内联脚本(Webpack/Vite/HTMX)必须显式带 nonce
你手动写的 <script nonce="xxx"> 能过,但 Webpack 的 runtime、Vite 的 HMR 初始化、HTMX 动态插入的脚本默认不带 nonce——它们照样被 CSP 拦,可能白屏或功能异常。
对应解法:
- Webpack:在入口 JS 最顶部设
window.__webpack_nonce__ = nonce(必须在__webpack_require__执行前) - Vite:不能只靠
index.html模板插值,需在vite.config.ts中配置build.rollupOptions.output.manualChunks并确保内联 runtime 脚本由模板渲染注入 - HTMX:其
evalScript函数会丢掉 DOM 内部的script.nonce,需手动补:newScript.nonce = script.nonce
strict-dynamic 是动态创建脚本的唯一合法路径
仅靠 nonce 只能放行初始的内联 <script>;后续用 document.createElement('script')、eval 或 innerHTML 创建的脚本,默认仍被拦截。
要让它继续执行,CSP 响应头里必须启用 'strict-dynamic':
- 写法是:
script-src 'nonce-xxx' 'strict-dynamic'(顺序无关,但二者缺一不可) - 启用后,由已获许可脚本创建的新脚本自动继承信任链,无需再配 nonce
- 注意:
'strict-dynamic'会弱化域名白名单(如'self'),需配合使用,不能单独存在
nonce,而是服务端是否稳定下发响应头、头里是否真含 'nonce-xxx'、每次生成的值是否真的随机且 Base64 编码合规——三者脱节,nonce 就只是 HTML 里一段无效字符串。



















