nonce值必须由服务端动态生成且严格匹配:响应头与script标签的nonce须逐字节一致,禁止硬编码、前端生成或复用;node.js用crypto.randombytes,python用secrets.token_urlsafe;ssr/cdn不可缓存;webpack/vite runtime脚本需显式注入nonce;strict-dynamic须与nonce共存才能放行动态脚本。

nonce 值必须由服务端每次请求动态生成
硬编码、前端生成(如 Math.random() 或 Date.now())、复用旧值,都会让 CSP 彻底失效。浏览器不报错,但脚本静默被拦——这不是配置没生效,而是令牌校验失败。
- Node.js 推荐:
crypto.randomBytes(16).toString('base64').replace(/[+/=]/g, '')(去掉 Base64 中的+、/、=,避免 HTML 解析异常) - Python 推荐:
secrets.token_urlsafe(16) - 绝对不要缓存 nonce 值:SSR 模板、CDN 缓存、静态化页面若没刷新 nonce,等于把“一次性通行证”发给所有人
响应头和 script 标签的 nonce 值必须逐字节一致
浏览器做的是字符串精确比对,任何差异都会导致 Refused to execute inline script。不是“差不多就行”,是“一个字符都不能差”。
- 检查点:用浏览器 DevTools → Network → Headers 查看
Content-Security-Policy,确认含script-src 'nonce-abc123'(单引号包裹,无空格、无大小写混用) - 再查页面源码(右键 → View Page Source),搜索
<script nonce="</code">,确认属性值完全等于响应头里的 <code>abc123</script>(不含换行、制表符、全角符号、末尾空格) - 模板插值常见坑:
{{ nonce|safe }}在 Jinja2 中若nonce含双引号或&,可能提前闭合标签;EJS 用更稳妥
Webpack/Vite 的 runtime 脚本也得带 nonce
你手动加了 <script nonce="xxx"></script>,但 Webpack 的 __webpack_require__ 或 Vite 的 HMR 初始化逻辑仍是内联脚本,默认没 nonce——它们照样被拦,结果页面白屏或 hydration 失败。
- Webpack:在 HTML 模板最上方注入
window.__webpack_nonce__ = "xxx";,且确保打包配置中script-src包含对应'nonce-xxx' - Vite:不能只靠
define注入,需在index.html模板中用服务端变量写死:<script nonce="%nonce%">...</script>,并配合插件(如vite-plugin-csp)同步响应头 - React/Vue SSR 场景下,
dangerouslySetInnerHTML或v-html插入的脚本不会自动继承 nonce,必须显式传入当前请求的值
strict-dynamic 是动态脚本能跑起来的前提
只配 nonce 只能放行初始的那几个 <script nonce="xxx"></script>;后续用 document.createElement('script') 或 eval 创建的脚本,默认仍被拦。要让它合法执行,必须启用 strict-dynamic。
- 响应头必须同时包含:
script-src 'nonce-xxx' 'strict-dynamic'(顺序无关,但二者缺一不可) -
'self'和'strict-dynamic'共存时,'self'会被忽略——它只对非动态脚本起作用 -
strict-dynamic不豁免内联事件处理器(如onclick),这类仍需改用addEventListener,否则照样被拒
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











