服务端生成的nonce必须在html解析前注入标签,动态js添加无效;需确保服务端渲染、csp响应头、构建产物、iframe子文档五处nonce值完全一致且字符级匹配。

服务端生成的 nonce 必须在 HTML 解析前就写进 <script></script> 标签
浏览器在解析 HTML 阶段就完成 nonce 校验,JS 执行时早已结束。动态用 JS 给 <script></script> 补 nonce 属性完全无效。
常见错误写法:
<script id="boot">
document.getElementById('boot').setAttribute('nonce', 'abc123');
</script>
这不会被浏览器认可——因为解析器早已跳过该标签,校验已完成。
正确做法只能是服务端渲染时注入:
- Node.js(Express + EJS):
<script nonce="<%= nonce %>">window.config = { ... };</script> - Python(FastAPI + Jinja2):
<script nonce="{{ nonce }}">...</script> - 注意模板变量插值必须不转义引号和等号,否则
nonce="a/b+c=="可能被截断为nonce="a
关键检查点:查看页面「源代码」(不是 Elements 面板),确认 <script nonce="xxx"></script> 的值与响应头中 Content-Security-Policy: script-src 'nonce-xxx' 字符级完全一致(无空格、无换行、大小写敏感)。
Webpack/Vite 构建产物里的内联 runtime 脚本也要带 nonce
框架打包工具自动生成的内联脚本(如 Webpack 的 __webpack_require__ 启动逻辑、Vite 的 HMR 初始化代码)默认不带 nonce,CSP 会直接拦截,导致白屏或功能异常。
修复方式因构建工具而异:
使用 Puppeteer + Chrome 将 HTML 渲染为中文 PDF,自动处理图表等待、Tab 展开、动画、测高、白边消除、防分页,适用于看板、报表、网页和交互图表转 PDF。
- Webpack:在入口 JS 最顶部(
<script></script>标签内)声明全局变量__webpack_nonce__ = nonceValue,Webpack 会自动将它注入所有内联 runtime 脚本 - Vite:需在
vite.config.ts中配置build.rollupOptions.output.manualChunks,并确保 HTML 模板中手动注入带nonce的 runtime 脚本(不能依赖injectHtml插件自动补) - 不要用
dangerouslySetInnerHTML或v-html动态插入脚本——它们生成的<script></script>不会被自动赋予 nonce,必须显式传入当前请求的值
strict-dynamic 是让后续动态脚本合法执行的必要开关
仅靠 nonce 只能放行初始 HTML 中已存在的 <script nonce="xxx"></script>;后续用 document.createElement('script')、eval() 或 setTimeout("...") 创建的脚本,默认仍被拦截。
要让它继续执行,CSP 响应头必须启用 'strict-dynamic':
Content-Security-Policy: script-src 'nonce-abc123' 'strict-dynamic'
注意:
-
'strict-dynamic'必须和'nonce-xxx'同时存在,且写在同一条script-src指令里 - 它不兼容
'unsafe-inline',二者不能共存 - 它对旧版 Safari(
iframe 内部文档的脚本不能复用父页 nonce
<iframe src="javascript:false;"></iframe> 这类写法触发的是 JavaScript URL 执行,CSP 将其视为“内联脚本”,但 nonce 属性加在 <iframe></iframe> 元素上毫无作用——nonce 只绑定到 <script></script> 标签本身。
正确做法是向 iframe 的 contentDocument 注入带 nonce 的 <script></script>:
const iframe = document.querySelector('iframe');
const doc = iframe.contentDocument;
const script = doc.createElement('script');
script.nonce = currentNonce; // 必须传入服务端为本次响应生成的值
script.textContent = 'console.log("inside iframe");';
doc.head.appendChild(script);
难点在于:iframe 的 contentDocument 可能尚未 ready,且跨域时无法访问。所以更稳妥的方式是服务端直接渲染 iframe 的 HTML 内容,并在其中写死 <script nonce="xxx"></script>。
真正难盯的从来不是怎么写 nonce,而是确保服务端生成、响应头、HTML 标签、构建产物、iframe 子文档这五处值在每一次 HTTP 响应中都毫秒级对齐。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










