加了 nonce 仍被拦截,根本原因是服务端与前端未对齐:响应头缺失或大小写错误、script 标签 nonce 值不一致、误用 meta 标签(不支持 nonce)、CSP 中 nonce 格式错误(须单引号包裹且无空格)、未正确组合 strict-dynamic 等策略、服务端未安全生成并同步 nonce、构建工具注入脚本或动态 HTML 漏掉 nonce。

为什么加了 nonce 还被浏览器拦?
根本不是标签写错了,而是服务端和前端没对齐。浏览器报 Refused to execute inline script 时,90% 是因为这三件事至少错了一件:
- 响应头漏了
Content-Security-Policy: script-src 'nonce-xxx',或者写了'nonce-XXX'(大小写敏感) -
<script nonce="xxx">的值和响应头里的'nonce-xxx'不完全一致(比如多了空格、引号被模板截断、含换行) - 用了
<meta http-equiv="Content-Security-Policy">—— 这个标签**不支持 nonce**,必须走 HTTP 响应头
script-src 指令里怎么写 nonce 才生效
不能只写 'nonce-abc123' 就完事。它必须和其他策略共存,且顺序和拼写都影响行为:
- 必须用单引号包裹,且中间不能有空格:
'nonce-27f434278e6b79578d5c9f0a321c4567'✅,"nonce-..."或nonce-...❌ - 如果同时要用
strict-dynamic,得把两者并列放在script-src里:script-src 'nonce-xxx' 'strict-dynamic' -
script-src 'self' 'nonce-xxx'是常见组合;但若写成script-src 'self' 'strict-dynamic','self'会覆盖'strict-dynamic',导致动态创建的<script>全部失效
服务端怎么生成和注入 nonce 才安全
不能前端拼、不能 localStorage 读、不能 URL 传——nonce 必须由服务端每次响应时动态生成,并同步到两个地方:
- 用高熵随机源生成,比如 Node.js 的
crypto.randomBytes(32).toString('hex'),或 Python 的secrets.token_hex(32) - 响应头中完整写出指令:
Content-Security-Policy: script-src 'self' 'nonce-27f434278e6b79578d5c9f0a321c4567' - HTML 模板里显式插值:
<script nonce="{{ nonce }}">init();</script>,注意确认模板引擎不转义引号或等号(Jinja2 要用{{ nonce|safe }},EJS 要避免<%== %>外的额外过滤)
哪些脚本会“自动漏掉” nonce?
你手动加了 nonce,但浏览器照样报错,大概率是这些脚本没带值:
立即学习“前端免费学习笔记(深入)”;
- Webpack/Vite 的 runtime 脚本(HMR、chunk 加载逻辑),它们由构建工具自动注入,不继承你手写的
nonce - React/Vue SSR 中用
dangerouslySetInnerHTML或v-html动态插入的内联脚本,不会自动绑定当前请求的nonce,必须手动传入 - 所有内联事件处理器:
<button onclick="submit()">无法挂nonce,必须重构为addEventListener
真正难的不是加一个属性,而是确保整个链路——从服务端生成、响应头写入、模板渲染、构建注入、客户端 hydration——每一环的值都严格一致,差一个字符就全盘失效。



















