如何利用 Subresource Integrity (SRI) 技术保障外部脚本安全,防止篡改

首先需要明确一个核心认知:切勿将 SRI 视为一个“开启即安全”的万能开关。它的防护机制仅在脚本地址固定、哈希值绝对精确、且浏览器完全支持的前提下才会生效。若使用场景不当,SRI 反而可能成为功能故障的源头,直接阻断关键脚本加载,导致页面出现白屏或功能异常。
深入解析 SRI 的 integrity 属性:它究竟校验什么?
要掌握 SRI 的精髓,关键在于理解 integrity 属性的工作原理。它的核心任务并非校验域名或 SSL 证书的有效性,而是对远程资源(例如托管在 CDN 上的 lodash.min.js)的响应体内容本身,执行一次严格的密码学哈希比对。浏览器在下载完脚本文件后,会立即计算其内容的 SHA-256、SHA-384 或 SHA-512 哈希值,并与你在 integrity 属性中预先配置的哈希值进行匹配——一旦两者不符,浏览器将直接拒绝执行该脚本,且不会触发任何 onerror 事件。
- 该属性必须与
crossorigin属性搭配使用。缺少此搭档,浏览器会直接忽略integrity,导致所有安全防护措施失效。 - 哈希值必须与服务器返回的实际响应体内容达到比特级别的完全一致,包括换行符、BOM 头、压缩状态等细节。本地构建时,代码压缩工具(minifier)的微小差异都可能导致校验失败。
- 目前,SRI 仅支持
和这两种 HTML 标签。对于通过fetch()API 发起的请求或使用document.createElement('script')动态创建的脚本标签,SRI 机制无法生效。
如何正确生成 integrity 哈希值(避免使用在线工具的误区)
生成可靠的哈希值,最稳妥的方法是直接针对目标 URL 返回的原始响应体内容进行计算,而非对本地存储的文件副本或 HTML 源码进行操作。原因在于:CDN 返回的内容可能经历了不同的编码处理、gzip 压缩,甚至包含 HTTP 重定向,这些因素都会影响最终计算出的哈希结果。
- 推荐使用以下命令获取真实响应体的 SHA-384 哈希值:
curl -sL https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js | openssl dgst -sha384 -binary | openssl base64 -A。 - 应避免使用类似
shasum -a 384 lodash.min.js | awk ‘{print $1}’ | xxd -r -p | base64的命令,因为它校验的是本地文件副本,而非 CDN 实际分发给浏览器的动态内容。 - 若脚本启用了 Brotli 或 gzip 压缩,
curl命令默认可能会自动解压。虽然浏览器通常校验的是解压后的内容(使用curl -sL默认参数即可),但为确保严谨,可添加--compressed参数,并核对响应头中的content-encoding字段进行确认。
crossorigin 属性:应设置为 "anonymous" 还是 "use-credentials"?
对于绝大多数公开的 CDN 资源,设置 crossorigin="anonymous" 即可满足需求。此配置的含义是:发起跨域请求时不携带 Cookies 或用户认证信息,同时允许浏览器执行 SRI 校验。若设置为 "use-credentials",则请求会附带用户凭据,但这要求服务端必须明确返回 Access-Control-Allow-Origin: 你的具体域名(注意,不能使用通配符 *),否则跨域请求将直接失败。
- 主流公共 CDN 如 jsDelivr、unpkg、cdnjs 均支持
anonymous模式,并会在响应头中包含access-control-allow-origin: *。 - 如果你的静态资源托管在自有后端服务器上,且需要用户登录态才能访问,则必须使用
use-credentials模式。同时,后端必须配置返回access-control-allow-origin: https://your-site.com(同样不能为*)以及access-control-allow-credentials: true。 - 需要高度警惕的是,遗漏
crossorigin属性是导致 SRI 失效最常见的原因之一,且浏览器控制台通常不会抛出明确错误,只会静默忽略integrity属性。
线上环境 SRI 校验失败:如何快速诊断与定位问题
一旦 SRI 校验失败,脚本加载会立即被中断。浏览器控制台通常会抛出类似 Failed to find a valid digest in the 'integrity' attribute 的错误信息,但它不会明确指出是哪个脚本出了问题——当页面存在多个带有 integrity 属性的 标签时,就需要逐一排查。
- 打开浏览器开发者工具,进入 Network(网络)面板,找到对应的 JavaScript 请求,查看 Preview(预览)或 Response(响应)标签页,确认返回的内容是否与你当初生成哈希时所用的内容完全一致(例如,检查是否多出空格、末尾换行符是否相同)。
- 可临时移除
integrity和crossorigin属性,测试脚本是否能正常加载。此步骤主要用于排除网络问题或跨域权限配置问题。 - 使用
curl -I命令检查响应头,确认是否存在content-security-policy策略干预,或是否因strict-transport-security导致 HTTP 到 HTTPS 的重定向,从而改变了最终的响应体内容。
实际上,最棘手的挑战往往不是生成哈希值本身,而是在资源更新后忘记同步更新 integrity 值,或是由于 CDN 的缓存策略导致旧的哈希值匹配了新的文件内容。这类问题通常具有滞后性,可能在灰度发布数小时后才突然爆发,并且很难通过常规的自动化测试覆盖,因为测试环境的 CDN 响应体很可能与生产环境存在差异。
