第三方广告脚本加了nonce仍被拦截,是因为浏览器仅校验html解析时已存在的,动态创建或iframe子文档中nonce未同步将导致静默丢弃。

第三方广告脚本为什么加了 nonce 还被拦截
不是广告 SDK 写错了,而是浏览器在 HTML 解析阶段就完成了 nonce 校验——它只认服务端渲染时已写死在源码里的 <script nonce="xxx"></script>。你用 JS 动态创建 document.createElement('script')、再手动设 script.nonce = 'xxx',或者靠 v-html/dangerouslySetInnerHTML 插入的脚本,**全部无效**。更隐蔽的是:广告容器常嵌在 iframe 里,而 iframe 子文档的 CSP 响应头必须单独配,且 nonce 值要和服务端主页面完全一致(字符级匹配),否则静默丢弃,连控制台都不报错。
nonce 必须和 strict-dynamic 配合才能让广告脚本动起来
只配 Content-Security-Policy: script-src 'nonce-abc123',广告 SDK 后续用 eval() 加载逻辑、或动态插入 <script src="ad.js"></script>,照样被拦。真正起作用的是组合:script-src 'nonce-abc123' 'strict-dynamic'。但注意两个硬约束:
- 首个执行的脚本(通常是广告初始化入口)必须是服务端渲染的内联
<script nonce="abc123"></script>,不能是空标签或仅声明变量 - 广告 SDK 自己创建的后续脚本,必须显式设置
newScript.nonce = 'abc123',值不能从 localStorage 或 URL 读,也不能拼接 - 如果广告脚本走
fetch()+eval()路径,'strict-dynamic'不覆盖eval,得改用Function构造器并确保调用栈可信
广告 iframe 子文档的 nonce 链路最容易断在哪
微信公众号、App WebView 等环境里,广告 iframe 白屏八成是因为子文档的 nonce 断链。关键检查点有三处:
- 主页面响应头中的
Content-Security-Policy是否被 Nginx/CDN 中间件覆盖或截断(比如只保留前半段) - iframe 的
src是 data URL 还是真实 URL?data URL 无法携带 HTTP 响应头,nonce失效;必须用独立 HTTP 请求加载子 HTML,并确保该响应头含script-src 'nonce-xxx' - 子文档 HTML 源码里
<script nonce="xxx"></script>的值,是否和服务端返回的响应头中'nonce-xxx'**逐字相同**(大小写、Base64 填充符 =、无空格)?模板引擎如 Jinja2 漏了|safe会把=转成=,导致不匹配
Vite/React 中广告脚本的 nonce 注入不能只靠模板插值
在 Vite 构建的广告 SDK 里,光在 index.html 模板写 <script nonce="{{ nonce }}"></script> 不够。Vite 自动生成的 <link rel="modulepreload"> 和 runtime 脚本默认不带 nonce,而广告初始化常依赖这些资源。正确做法是:
- Vite 配置启用
build.rollupOptions.output.manualChunks,把广告 SDK 入口逻辑打包进一个明确 chunk - 在 HTML 模板中,用服务端传入的
nonce显式包裹该 chunk 的<script></script>标签,而不是依赖injectHtml插件自动补 - React SSR 场景下,
nonce必须作为context透传到组件层,广告容器组件内写<script nonce="{nonce}">window.adSdkInit(...)</script>,不能靠 hydration 后 JS 补属性 - 禁用
html.cspNonce静态配置——它构建时生成固定值,违反nonce“每次请求唯一”原则
nonce,且不能复用。任何一层漏掉响应头、拼错大小写、或被 CDN 缓存,整个链路就静默失效。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











