如何在客户端验证签名并拒绝被篡改资源

解读

面试官抛出此题,并非让候选人背诵“HTTPS 就完事了”。在国内前端工程化场景里,构建产物(JS、CSS、图片、WASM) 经常放在第三方 CDN公司自建的 OSS + 边缘节点,一旦回源链路被污染或 CDN 配置被误改,页面就可能加载到被植入挖矿脚本的资源。
Grunt 作为构建阶段的老牌任务运行器,其核心价值在于“在上线前就给出签名,并让浏览器在运行时能校验”。因此,题目真正想考察的是:

  1. 你能否用 Grunt 插件在构建流水线里生成带签名的资源表
  2. 你能否让浏览器在第一时间(资源加载前) 完成校验,拒绝执行/渲染被篡改的文件;
  3. 你对国内浏览器兼容性、性能损耗、合规性(国密、等保)是否有落地经验。

知识点

  1. 子资源完整性(SRI)
    W3C 标准,通过 integrity 属性把“文件内容 → 哈希 → base64”写死在 HTML 标签里,浏览器下载后异步校验,失败则触发 error 事件并拒绝执行
  2. Grunt 插件生态
    • grunt-sri:遍历 dist,生成 integrity 清单;
    • grunt-cdnify:把本地路径批量改成 CDN 地址并自动插入 integrity;
    • grunt-manifest:生成带版本号的 json 清单,供 ServiceWorker 做二次校验。
  3. 国内 CDN 的特殊性
    • 部分云厂商会在回源时自动压缩(gzip/br),导致浏览器算出的哈希与构建时不一致;
    • 某些省份运营商会插入广告脚本,SRI 会直接阻断,业务必须第一时间感知并报警
  4. 国密 SM3 场景
    政务、金融项目要求“SM3 替代 SHA-384”,需要把 grunt-sri 的哈希算法换成 sm-crypto,浏览器端用 wasm 实现 SM3 校验
  5. 性能与降级
    • 对大型 SPA,integrity 属性会增加 60~80 bytes/文件,首屏可接受
    • 若需兼容 IE11,可降级为动态 script 加载 + 手动哈希校验,失败则 location.reload() 强制回源。

答案

“我会把验证流程拆成构建、发布、运行时三段,全部用 Grunt 自动化。
第一步,构建阶段:

  • grunt-contrib-uglify、grunt-contrib-cssmin 压缩后,接着跑 grunt-sri,对 dist 里所有 .js.css.wasm 计算 SHA-384 并生成 sri.json
  • 再用 grunt-processhtml 把模板里的占位符 <!--SRI--> 批量替换成带 integrity 的 script/link 标签;
  • 如果客户要求国密,我就把 grunt-sri 的哈希函数换成 sm3,并在浏览器里引入 sm-crypto.min.js,用 postMessage 把文件 ArrayBuffer 发到 WebWorker 做异步校验,主线程零阻塞

第二步,发布阶段:

  • sri.json 随构建产物一起推到 OSS,设置 HTTP 头 Cache-Control: no-transform,防止 CDN 擅自压缩篡改内容;
  • 在边缘节点配置 ETag = 强哈希,与 integrity 值保持一致,一旦回源被改,ETag 对不上立即 4xx,方便快速定位。

第三步,运行时:

  • 关键入口 JS(如 app.*.js)使用 rel="preload" 提前加载,一旦 integrity 校验失败,浏览器会触发 window.onerror,我在全局捕获后上报 Sentry,并调用 sessionStorage.setItem('__block__',1)后续所有异步 chunk 不再加载,直接跳转到静态降级页;
  • 对于图片、字体等非执行资源,用 ServiceWorker 拦截 fetch 事件,把 URL 去 sri.json 里查哈希,失败则返回 403,避免被运营商插入广告图。

整个方案在 2023 年某省政务云上线,首屏时间增加 < 20 ms零误拦截,并通过等保三级测评。”

拓展思考

  1. 双签名机制
    如果项目需要离线包 + 在线热更新,可在 Grunt 里再跑一道 RSA 签名,把 signature.bin 打进 zip 包,客户端先用国密 SM2 验签离线包,再通过 SRI 验在线资源,两层防线互不干扰
  2. 动态资源怎么办
    服务端渲染vendor.dll.js(哈希每次构建会变),可把 integrity 写入 全局 window.SRI,由 SSR 模板直接输出,避免 CDN 缓存旧版本
  3. 灰度与回滚
    在 Grunt 后置任务里把 sri.json 上传到配置中心(Nacos/Apollo)灰度发布时只让 5% 节点拉取新哈希,一旦监控发现校验失败率 > 0.1%,立即回滚配置中心版本,实现秒级回退