csp nonce 是服务端生成的唯一、一次性、加密安全的 base64 字符串,必须同步注入响应头和 html script 标签;前端拼接(如 math.random)或复用/硬编码均失效,须配合 'strict-dynamic' 才支持动态脚本。

什么是 CSP nonce,它为什么不能靠拼字符串生成
nonce 不是随便生成的随机字符串,而是必须由服务端在每次响应时生成唯一、一次性、加密安全的 Base64 编码值,并同步写入 Content-Security-Policy 响应头和 HTML 标签中。浏览器会严格比对二者是否完全一致,任意字符差异(比如末尾多一个空格、换行、大小写错误)都会导致脚本被拒绝执行。
常见错误包括:
- 前端用
Math.random()或Date.now()拼接生成 nonce 值——浏览器直接无视,因为不满足“服务端可信源”要求 - 服务端复用同一个 nonce 多次(例如缓存了 HTML 模板但没刷新 nonce)——第二次加载时脚本失效
- 把 nonce 值硬编码在前端构建产物里(如 Vite/webpack 构建时注入)——失去“一次性”语义,CSP 策略形同虚设
如何正确注入 nonce 到 <script></script> 和响应头
关键在于服务端渲染(SSR)或模板引擎阶段完成注入,且保证 HTML 中的 nonce 属性值与响应头中的 script-src 'nonce-xxx' 完全一致。
以 Express + EJS 为例:
// 服务端生成(Node.js)
const crypto = require('crypto');
const nonce = crypto.randomBytes(16).toString('base64');
res.setHeader('Content-Security-Policy', `script-src 'nonce-${nonce}' 'strict-dynamic'`);
res.render('index', { nonce });
模板中使用:
<script nonce="<%= nonce %>">
console.log('this runs');
</script>
注意点:
-
nonce值不能带双引号或尖括号等 HTML 特殊字符,crypto.randomBytes().toString('base64')是安全的 - 必须同时启用
'strict-dynamic',否则即使有 nonce,后续通过eval或innerHTML创建的脚本仍会被拦截 - 如果用了 Webpack/Vite 的 inline script(如
__webpack_require__启动代码),也要确保它们的<script></script>标签同样携带该 nonce
动态创建的脚本怎么合法执行
有了 'strict-dynamic',浏览器允许由已获许可的脚本(即带正确 nonce 的初始 <script></script>)创建的新脚本继续执行,无需再次校验 nonce。
这意味着你可以这样写:
const s = document.createElement('script');
s.textContent = 'alert("allowed");';
document.head.appendChild(s); // ✅ 允许
但这些方式依然失败:
-
eval('alert()')—— 除非显式开启'unsafe-eval'(不推荐) -
setTimeout('alert()', 100)—— 字符串形式的定时器被拦截 -
element.innerHTML = '<script>...</script>'—— DOM 解析出的 script 不继承父上下文的 nonce
绕过限制的可行做法:
- 用
textContent+appendChild(如上例) - 用
import('./module.js')动态导入 ES 模块(现代浏览器支持,且受'strict-dynamic'保护) - 预置带 nonce 的空
<script id="dynamic-root"></script>,运行时改写其textContent
开发调试时容易忽略的 CSP 细节
本地开发常跳过 CSP,但上线前漏掉几个关键配置就会让脚本静默失败,且控制台只报一条模糊的 Refused to execute inline script。
务必检查:
- 响应头中
Content-Security-Policy是否真的生效(不是Content-Security-Policy-Report-Only) - 是否遗漏了
'strict-dynamic'—— 没它,动态脚本全部被拦,无论有没有 nonce - 是否错误添加了
'self'或其他源(如https:)——它们会覆盖'strict-dynamic'的行为 - HTTP/2 推送的资源若含内联脚本,也需单独携带 nonce 属性,不能复用主文档的
最麻烦的其实是第三方 SDK:有些库会自己 document.write 或插入无 nonce 的 script 标签,这类必须联系厂商提供 CSP-safe 版本,或改用 sandboxed iframe 隔离。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











