integrity属性防CDN劫持仅在三条件同时满足时生效:资源跨域、integrity值基于CDN实际响应体计算、显式声明crossorigin="anonymous",缺一则静默失效;真正劫持时浏览器报“The resource <url> was blocked because integrity check failed”。

integrity 属性在 CDN 节点被劫持时,**只有当它真正生效时才起作用;否则就是摆设,劫持成功后脚本照常执行,浏览器连 warning 都不报**。
integrity 生效的三个硬性条件缺一不可
浏览器只在同时满足以下三点时才会校验哈希、拦截篡改:
-
script标签必须加载跨域资源(比如https://cdn.jsdelivr.net/...) -
integrity值必须基于该 CDN 实际返回的响应体字节流计算(不是本地文件、不是在线工具生成的“看起来像”的哈希) - 必须显式声明
crossorigin="anonymous"(写成crossorigin=""或crossorigin="true"都无效)
漏掉任意一条,浏览器就静默跳过校验——你看到的仍是白屏或功能异常,控制台只有 Failed to find a valid digest in the 'integrity' attribute 这类提示,而不是明确的拦截信号。
真正被劫持时浏览器会报什么错误
只有当上述三个条件全满足,且 CDN 返回的内容被篡改(哪怕只插入一个空格或 BOM),浏览器才会触发阻断,并在控制台显示:
立即学习“前端免费学习笔记(深入)”;
The resource <url> was blocked because integrity check failed
这个错误意味着:请求发出去了、响应收到了、哈希比对失败、脚本被丢弃、连 onerror 回调都不会触发。页面可能直接白屏或功能缺失,没有任何降级机会。
注意:Failed to find a valid digest 不代表被劫持,只说明 integrity 值格式错误(比如前缀写成 SHA384- 或哈希是 hex 编码);而 integrity check failed 才是校验真起了作用、且内容确实不对。
为什么必须用 curl 直取 CDN 响应体生成哈希
本地 npm install 下来的文件、构建产物目录里的压缩版、甚至从 GitHub Release 页面下载的 .js,都和 CDN 实际返回的字节流不一致——CDN 可能自动加 BOM、按 UA 返回不同版本、启用 Brotli/gzip 后再解压、或做重定向。这些差异都会导致哈希完全不匹配。
可靠命令(以 jsDelivr 为例):
curl -sL https://cdn.jsdelivr.net/npm/vue@3.4.21/dist/vue.global.js | openssl dgst -sha384 -binary | openssl base64 -A
关键点:
-
-sL:静默 + 跟随重定向,避免哈希算到 302 响应体上 - 不手动解压:
curl默认已处理 gzip/Brotli,输出的就是浏览器实际拿到的解压后字节 - 哈希值必须是 Base64 编码(不是 hex),前缀严格小写:
sha384-(SHA384-或sha384:都无效)
crossorigin="anonymous" 为什么不能省
SRI 校验强制走 CORS 流程。没有 crossorigin,浏览器根本不会把响应体暴露给校验逻辑——它连内容都读不到,自然无法比对哈希。这不是 bug,是规范设计:安全机制必须建立在可验证的传输通道上。
绝大多数公开 CDN(jsDelivr、unpkg、cdnjs)都返回 Access-Control-Allow-Origin: *,所以 crossorigin="anonymous" 就够用。如果你的静态资源托管在需登录态访问的私有后端,才考虑 crossorigin="use-credentials",但此时服务端必须同步返回 Access-Control-Allow-Credentials: true 和明确域名的 Access-Control-Allow-Origin,否则请求直接失败。
真正难的不是加属性,而是确保每次上线的哈希都绑定到那个精确字节流——构建工具差异、CDN 压缩策略、BOM 插入、甚至 Node.js 版本导致的 base64 编码细节,都可能让哈希失效。



















