nonce本身就是csp中允许内联脚本的安全白名单机制,需服务端每次动态生成高熵随机值,严格匹配响应头script-src指令与标签的值,缺一不可,且不支持设置。

直接用 script 标签的 nonce 属性防御 XSS,核心不是“配合”CSP 白名单,而是它本身就是 CSP 中允许内联脚本的**安全白名单机制**——比 'unsafe-inline' 安全,比哈希更灵活。
nonce 是怎么起作用的
浏览器只执行同时满足两个条件的内联脚本:
① <script nonce="abc123">...</script> 标签里写了正确的 nonce 值;
② HTTP 响应头 Content-Security-Policy 中明确包含 'nonce-abc123'。
只要缺一,脚本就被拦截。攻击者即使能往页面注入 <script>alert(1)</script>,也拿不到服务端每次生成的随机 nonce,自然无法绕过。
服务端必须动态生成且严格匹配
- 每次 HTTP 响应都要生成一个高熵、一次性、不可预测的随机字符串(比如 32 位十六进制)
- 把这个值同时写入两处:
– 响应头:Content-Security-Policy: script-src 'self' 'nonce-27f434278e6b79578d5c9f0a321c4567'
– HTML 中对应<script></script>标签:<script nonce="27f434278e6b79578d5c9f0a321c4567">console.log('ok');</script> - 值必须完全一致:大小写敏感、不能有空格或换行、不能用全角引号
-
不能硬编码,不能复用,不能通过
<meta http-equiv="Content-Security-Policy">设置(该方式不支持 nonce)
常见失效原因和应对
- 模板引擎插值出错:比如 Jinja2 或 EJS 中未做 HTML 安全转义,导致 nonce 字符串被截断或注入失败 → 检查变量渲染是否原样输出
- 前端构建工具自动注入脚本没带 nonce:Webpack/Vite 的 HMR、runtime 脚本默认不带 → 需配置
__webpack_nonce__或使用script-src-elem分离控制 - CSP 头重复或冲突:同时设置了
Content-Security-Policy和Content-Security-Policy-Report-Only,但只有其中一个含 nonce → 浏览器按最严策略执行,确保所有生效头都包含对应 nonce - 遗漏其他指令:比如只配了
script-src,但没设default-src 'none',可能导致字体、iframe 等资源绕过限制 → 推荐先设兜底再逐项放开
在主流框架中安全落地
Laravel + Vite 可直接调用 Vite::cspNonce(),它自动生成并同步 nonce 到响应头和标签;FastAPI 可通过中间件注入 CSP 头,并在模板中动态插入 nonce;React/Vue 项目若需服务端渲染,也要确保 SSR 输出的 <script></script> 标签携带当前请求的 nonce 值,且不被客户端 hydration 覆盖。











