nonce属性必须与HTTP响应头中的Content-Security-Policy严格配对,字符级完全一致(含大小写、空格、引号),服务端每次请求须用安全随机函数生成新nonce并同步注入响应头和HTML标签;框架自动注入脚本需额外配置nonce传递机制;启用'strict-dynamic'是放行动态脚本的前提,且须与'nonce-xxx'并列声明;所有中间件/CDN可能覆盖CSP头,上线前须curl验证。

nonce属性必须和HTTP响应头严格配对
直接写nonce属性没用,浏览器只认两端完全一致的字符串:服务端在Content-Security-Policy响应头里声明'nonce-xxx',HTML里<script nonce="xxx"></script>的值必须字符级相同——大小写、空格、引号、换行都不能差。常见失败是响应头漏了单引号、写了'Nonce-xxx'(首字母大写)、或者模板插值时把nonce值里的"或&转义失败导致标签提前闭合。
服务端每次请求必须生成新nonce
硬编码、复用、前端生成(比如Math.random())全都不行。正确做法是:
- Node.js 用
crypto.randomBytes(32).toString('hex')(别用 base64,避免+//=) - PHP 用
bin2hex(random_bytes(32)) - Python 用
secrets.token_hex(32) - 生成后同步塞进两个地方:HTTP响应头 + HTML模板中对应
<script></script>或<style></style>标签
Vite/React/Vue等框架里容易漏掉的脚本
你手动写的<script nonce="xxx"></script>能过,但框架自动注入的脚本常被拦:
- Vite:需配置
html.cspNonce,否则 HMR 和 runtime 脚本没nonce - Webpack:得在入口 JS 执行前设全局变量
__webpack_nonce__ = nonce - React SSR:服务端渲染时要把
nonce作为 props 传下去,<script nonce="{nonce}"></script>不能靠 hydration 补 - Goober 等 CSS-in-JS 库:依赖
window.__nonce__,必须在加载库前定义
strict-dynamic不是可选开关,是动态脚本存活前提
只配'nonce-xxx'只能放行初始内联脚本;后续用document.createElement('script')或fetch().then(eval)创建的脚本,默认仍被拦。要让它活,响应头必须同时包含:
立即学习“前端免费学习笔记(深入)”;
Content-Security-Policy: script-src 'nonce-xxx' 'strict-dynamic'
注意:'strict-dynamic'必须和'nonce-xxx'并列,且首个执行的脚本(通常是首屏初始化逻辑)必须带这个nonce,否则整个链路断掉。
最常被忽略的是:所有中间件、CDN、反向代理都可能覆盖或删掉你精心设置的 CSP 响应头,上线前务必用curl -I直连后端确认最终发出的头里有且仅有你预期的那个'nonce-xxx'。



















