nonce值必须两端完全一致,差一个字符就失败;服务端生成、响应头配置、html标签三者需同步且来自同一源,并配合'strict-dynamic'启用动态脚本执行,同时覆盖构建工具、ssr框架及客户端动态脚本的nonce注入。

nonce值必须两端完全一致,差一个字符就失败
浏览器对nonce的校验是逐字节比对,不是模糊匹配。只要<script nonce="..."></script>里的字符串和响应头Content-Security-Policy中'nonce-xxx'部分不完全相同,脚本立刻被拦截,控制台报错Refused to execute inline script。
常见不一致点:
-
nonce="abc123 "(末尾空格) vs'nonce-abc123' -
nonce="ABC123"(大写) vs'nonce-abc123'(小写) - 模板插值时混入换行符:
nonce=""渲染出nonce="abc123\n" - 用了全角引号或等号,比如
nonce="abc123"(中文等号)
服务端生成 + 响应头 + HTML 标签三者必须同步
不能只在 HTML 里写nonce属性,也不能只在响应头里配'nonce-xxx'——二者缺一不可,且必须来自同一生成源。
正确做法(以 Express 为例):
- 用
crypto.randomBytes(16).toString('base64')生成高熵值,不拼接、不硬编码 - 调用
res.setHeader('Content-Security-Policy', `script-src 'nonce-${nonce}' 'strict-dynamic'`) - 把
nonce变量传给模板,在<script nonce="<%= nonce %>"></script>中直接插值 - 确保该
nonce也注入到 Webpack/Vite 的 runtime 脚本中(如设置__webpack_nonce__或配置vite.html.cspNonce)
strict-dynamic 不是可选开关,而是动态脚本执行的前提
加了nonce只解决首屏内联脚本,后续通过document.createElement('script')创建的脚本仍会被拦,除非启用'strict-dynamic'。
但注意:'strict-dynamic'必须和'nonce-xxx'共存于script-src,且首个执行脚本必须带该nonce,否则整个链路失效。
- 错误写法:
script-src 'self' 'strict-dynamic'→'self'会覆盖'strict-dynamic'语义 - 正确写法:
script-src 'nonce-abc123' 'strict-dynamic' -
onclick="..."这类内联事件处理器不受strict-dynamic保护,必须改用addEventListener
Vite/React/Vue SSR 中最容易漏掉的环节
构建工具自动注入的启动代码(如 Vite HMR、React hydration 脚本、Vue 的createApp调用)默认不带nonce,哪怕你手写了<script nonce="..."></script>,这些脚本依然被拦。
解决方案分两层:
- Vite:启用
html.cspNonce配置,并确认它实际注入到了<script></script>标签的nonce属性上,不只是<meta property="csp-nonce"> - SSR 框架:服务端渲染时,把
nonce传入组件上下文;客户端 hydration 前,确保所有动态插入的script都显式携带该值,不能依赖服务端已注入的 DOM 属性被复用 - Webpack:设
__webpack_nonce__ = nonce,同时确保Content-Security-Policy响应头包含对应'nonce-xxx'
真正难的不是生成 nonce,而是让每一个参与执行的脚本路径——从服务端模板、构建产物、runtime 注入、到客户端动态创建——全部拿到并正确使用同一个值。漏掉任意一环,CSP 就会在某个角落静默失效。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











