nonce值不匹配时浏览器直接拒绝执行,不是配置“没生效”,而是校验失败;只要script标签nonce属性与响应头中'nonce-xxx'逐字节不等(如空格、大小写、换行、全角符号等),脚本立即被拦截并报错。

nonce 值不匹配时浏览器直接拒绝执行,不是配置“没生效”,而是校验失败
浏览器对 nonce 的验证是原子级的:只要 script 标签里的 nonce 属性值和响应头 Content-Security-Policy 中的 'nonce-xxx' 字符串**逐字节不等**,脚本立刻被拦截,控制台报错 Refused to execute inline script because it violates the following Content Security Policy directive。这不是“策略没起作用”,而是令牌校验失败——就像门禁卡刷错了一位数字,闸机不会犹豫,直接拒入。
常见字符差异包括:
-
nonce="abc123 "(末尾空格) vs'nonce-abc123' -
nonce="ABC123"(大写) vs'nonce-abc123'(小写) - 模板插值时自动添加了换行或制表符,如
nonce=""渲染出nonce="abc123\n" - 用了全角引号:
nonce="abc123"(中文等号+全角双引号)
服务端必须每次请求生成新 nonce,硬编码或复用等于关闭防护
Nonce 的安全性完全依赖“一次性”语义。服务端若把 nonce 值存在常量、缓存模板、或跨请求复用(比如用 Date.now() 或 Math.random() 生成),攻击者只需一次窃取或预测,就能批量注入恶意脚本。
正确做法是:
- 使用加密安全随机源:Node.js 用
crypto.randomBytes(16).toString('base64'),Python 用secrets.token_urlsafe(16),Go 用crypto/rand.Read - 在请求生命周期内生成并传递:存于
res.locals(Express)、request.state(FastAPI)、或 SSR 上下文(Next.js / Vue SSR) - 绝不写死在前端构建产物里(如 Vite 配置中
define注入),那会变成静态字符串,失去防重放能力
Webpack/Vite 自动注入的 runtime 脚本必须显式携带 nonce
即使你手动给 HTML 里的 <script nonce="..."></script> 加了属性,Webpack 打包生成的启动代码(如 __webpack_require__)、Vite 的 HMR 逻辑、或框架 runtime 脚本,**默认不带 nonce**。它们属于“内联脚本”,同样受 CSP 拦截。
解决方案分两步:
- 告诉打包工具当前请求的
nonce值:Webpack 设置全局变量__webpack_nonce__ = 'abc123';Vite 需配合插件如vite-plugin-csp或手动在 HTML 模板中注入 - 确保构建输出的
<script></script>标签也带上该值——不能只靠 JS 变量,必须渲染进 HTML 的标签属性里 - 注意:仅设置
__webpack_nonce__不够,还要确认 Webpack 的script-src指令包含'nonce-abc123',否则变量无效
strict-dynamic 是动态脚本合法执行的必要条件,不是可选项
有了 nonce,只能让初始的 <script nonce="..."></script> 执行;后续 JS 动态创建的脚本(如 document.createElement('script'))仍会被拦截,除非 CSP 头里同时启用 'strict-dynamic'。
典型错误配置:script-src 'self' 'nonce-abc123' → 动态脚本仍被拒
正确配置:script-src 'self' 'nonce-abc123' 'strict-dynamic'
'strict-dynamic' 的作用是:允许由已获许可的脚本(即带正确 nonce 的初始脚本)创建的新脚本继承执行权限,无需再次校验 nonce。但前提是——那个初始脚本必须真实存在且通过了 nonce 校验。
这个指令不兼容老浏览器(IE 完全不支持,Safari 15.4+ 开始支持),线上环境需兜底 fallback 策略。

















