服务端必须每次响应生成加密安全且唯一的nonce,响应头与script标签的nonce值须字符级完全一致,框架内联脚本需显式注入nonce,启用strict-dynamic才能放行动态创建脚本。

服务端生成 nonce 必须每次请求都不同且加密安全
nonce 不是随便拼个字符串就行,它必须由服务端在每次 HTTP 响应时调用密码学安全随机源生成。硬编码、复用、前端生成(如 Math.random())都会导致 CSP 失效或被绕过。
常见错误包括:
• 用 new Date().getTime() 或 Math.random().toString(36) —— 可预测,浏览器直接忽略
• 缓存了带 nonce 的 HTML 模板(比如 CDN 或 SSR 静态化未刷新)—— 同一值多次使用,失去“一次性”意义
• Node.js 中漏掉清洗 Base64 字符:crypto.randomBytes(16).toString('base64') 可能含 +、/、=,需用 .replace(/[+/=]/g, '') 过滤
响应头与 script 标签的 nonce 值必须字符级完全一致
浏览器校验非常严格,任何微小差异都会静默失败——不报错、不警告,脚本直接被拦。这不是配置没生效,而是匹配失败。
典型不一致场景:
• 响应头里写 script-src 'nonce-abc123 '(末尾空格),HTML 里是 nonce="abc123"
• 大小写混用:nonce="Abc123" vs 'nonce-abc123'
• 模板插值时未做 HTML 安全转义,导致 nonce 值被截断(如含 & 或 ")
• 把 nonce 写进 <meta http-equiv="Content-Security-Policy"> —— 该方式**不支持 nonce**,只响应头有效
立即学习“前端免费学习笔记(深入)”;
React/Vue 等框架中动态插入的内联脚本也要带 nonce
框架启动代码(如 Webpack 的 __webpack_require__、Vite 的 HMR 初始化逻辑)常以内联形式注入,若没带 nonce,CSP 会拦截,页面白屏或功能异常。
解决路径:
• Webpack 用户需设置全局变量 __webpack_nonce__ = nonce(在入口 JS 执行前)
• Vite 用户需在 vite.config.ts 中配置 build.rollupOptions.output.manualChunks 并确保内联 runtime 脚本通过模板注入 nonce
• React Server Components 或 Vue SSR 渲染时,必须把 nonce 作为上下文变量传入,并在生成的 <script> 标签中显式写出属性,不能靠 JS 动态补 —— 浏览器解析 HTML 阶段已校验完毕
strict-dynamic 是让动态创建脚本合法执行的关键开关
仅靠 nonce 只能放行初始的内联 <script>;后续用 document.createElement('script') 或 eval 创建的脚本,默认仍被拦截。要让它继续执行,必须在 CSP 响应头中启用 'strict-dynamic'。
正确写法示例:
• Content-Security-Policy: script-src 'self' 'nonce-xxx' 'strict-dynamic'
• 对应 HTML:<script nonce="xxx">const s = document.createElement('script'); s.src = '/dynamic.js'; document.head.appendChild(s);</script>
注意:'strict-dynamic' 会忽略 'self' 和域名白名单,只信任由已授权脚本发起的加载行为;若同时需要外链资源,得叠加写成 script-src 'self' 'nonce-xxx' 'strict-dynamic' https://cdn.example.com
nonce 属性,而是三件事是否始终对齐:服务端是否稳定返回 Content-Security-Policy 响应头、头里是否包含对应 'nonce-xxx'、每次生成的值是否真正随机且编码合规。任一环节松动,整个链路就断了。



















