静态站点子资源完整性 SRI 落地实践:防止缓存劫持与资源篡改

举报
yd_282816122 发表于 2026/11/08 11:35:04 2026/11/08
【摘要】 前言前面我们完成了 Service Worker 缓存、缓存版本管理、缓存垃圾回收整套离线方案。但这里存在一个漏洞:就算我们做好了缓存更新与 GC,如果 CDN、静态存储链路被劫持,返回被篡改的 JS / CSS / 图片资源,浏览器依然会正常加载并执行。SRI(Subresource Integrity,子资源完整性) 就是用来解决这个问题的:提前在 HTML 中写入资源的哈希摘要,浏览...

前言

前面我们完成了 Service Worker 缓存、缓存版本管理、缓存垃圾回收整套离线方案。但这里存在一个漏洞:就算我们做好了缓存更新与 GC,如果 CDN、静态存储链路被劫持,返回被篡改的 JS / CSS / 图片资源,浏览器依然会正常加载并执行。

SRI(Subresource Integrity,子资源完整性) 就是用来解决这个问题的:提前在 HTML 中写入资源的哈希摘要,浏览器拉取资源后自动校验哈希,不一致则拒绝加载,从源头拦截篡改资源。本文基于我们的校园景点静态站点,完整落地 SRI,同时适配构建脚本、CI 流水线、离线缓存场景。

核心原理

SRI 通过 integrity 属性绑定资源哈希,搭配 crossorigin 属性使用:

  1. 构建阶段:对每一个静态资源(js、css、svg)生成 sha256/sha384/sha512 哈希;
  2. 注入到 HTML 的标签中:<script src="xxx.js" integrity="sha384-xxxx" crossorigin="anonymous"></script>;
  3. 浏览器下载资源后,实时计算资源哈希,和标签内摘要比对;
  4. 校验失败:直接阻止资源加载,抛出控制台错误,页面不会执行篡改后的代码。

注意:SRI 只对跨域资源或者开启 CORS 的同域 CDN 资源生效,crossorigin 不能省略。

一、Node 脚本:批量生成 SRI 哈希

新建 scripts/sri-generator.js,集成到我们已有的资源检查脚本中:

const fs = require('fs');
const path = require('path');
const crypto = require('crypto');

// 计算文件 integrity hash,默认 sha384
function getSriHash(filePath, algo = 'sha384') {
  const buffer = fs.readFileSync(filePath);
  const hash = crypto.createHash(algo).update(buffer);
  const base64 = hash.digest('base64');
  return `${algo}-${base64}`;
}

// 扫描目录,输出资源路径+哈希
function scanAssets(dir, extList = ['.js', '.css']) {
  const result = {};
  const files = fs.readdirSync(dir, { withFileTypes: true });
  for (const f of files) {
    const fullPath = path.join(dir, f.name);
    if (f.isDirectory()) {
      Object.assign(result, scanAssets(fullPath, extList));
    } else if (extList.includes(path.extname(f.name))) {
      const relPath = path.join('/assets', f.name);
      result[relPath] = getSriHash(fullPath);
    }
  }
  return result;
}

// 写入 sri-manifest.json,给HTML注入脚本读取
const assetMap = scanAssets('./dist/assets');
fs.writeFileSync('./dist/sri-manifest.json', JSON.stringify(assetMap, null, 2), 'utf8');
console.log('✅ SRI manifest generated');

运行命令:

node scripts/sri-generator.js

输出 sri-manifest.json,作为构建产物清单,后续模板引擎读取这个清单自动给标签加上 integrity。

二、HTML 模板改造示例

原始标签:

<link rel="stylesheet" href="/assets/style.css">
<script src="/assets/main.js"></script>

注入 SRI 之后:

<link rel="stylesheet" href="/assets/style.css"
      integrity="sha384-xxxxxxxxxxxxxxxxxxxx"
      crossorigin="anonymous">
<script src="/assets/main.js"
        integrity="sha384-yyyyyyyyyyyyyyyyyyyy"
        crossorigin="anonymous"></script>

图片资源一般不建议开启 SRI:图片篡改风险低,而且图片更新后哈希会变动,容易和缓存策略冲突;只对可执行、可渲染的脚本、样式表开启。

三、和现有 Workbox 离线缓存联动的关键约束

这里有一个非常容易踩坑的组合问题:SRI + Service Worker 缓存

  1. 资源更新之后,文件内容变了 → 哈希一定会变;
  2. HTML 里面的 integrity 同时更新;
  3. 如果 SW 缓存了旧版本资源,新 HTML 拿到新哈希,去加载缓存里的旧资源,哈希校验失败 → 资源加载失败。

✅ 配套策略:

  • 资源文件名加入内容哈希(main.8f2ac.js),文件内容变化则文件名变化,天然隔离新旧资源;
  • SRI 哈希和资源内容哈希保持同步,在构建阶段一起生成;
  • 缓存 GC 策略继续清理旧命名的资源缓存,避免缓存池膨胀;
  • 不要对 HTML 入口页面做长期强缓存,保证用户能拿到带最新 integrity 的 HTML。

四、CI 流水线集成与校验卡点

在 GitCode CI 中增加两个检查步骤:

  1. 构建产物必须生成合法的 sri-manifest.json,缺少则构建失败;
  2. 扫描所有 HTML,校验引用的资源是否都存在对应的 integrity 字段,缺失直接阻断发布;
  3. 禁止手动硬编码修改 integrity 值,所有哈希由脚本自动生成。

简易校验片段:

// 检查HTML中资源是否都有integrity
function checkSriInHtml(html, manifest) {
  const assetUrls = [...html.matchAll(/href|src="(\/assets\/.+?)"/g)].map(m=>m[1]);
  for(const url of assetUrls) {
    if(['.js','.css'].some(e=>url.endsWith(e)) && !manifest[url]) {
      throw new Error(`缺少SRI哈希:${url}`);
    }
  }
}

五、工程权衡与边界

优势

  • 防御 CDN 劫持、中间人篡改静态脚本样式;
  • 浏览器原生能力,不需要额外引入 SDK,性能损耗极低;
  • 和我们现有的资源路径白名单、构建检查脚本、缓存体系完美串联。

局限 & 坑点

  1. 资源微调一点点,哈希就会失效,适合内容不可随意变更的静态资产;
  2. 动态拼接、远程第三方脚本(非自己管控)不能加 SRI;
  3. 必须配套 CORS,很多免费静态托管平台需要确认跨域头配置;
  4. 图片、字体等大资源开启 SRI 收益很低,维护成本更高。

六、异常降级方案

SRI 校验失败时浏览器默认直接丢弃资源,页面容易白屏。我们可以增加兜底监控:

  • 监听 onerror 事件捕获 SRI 校验失败;
  • 上报异常埋点到性能统计接口;
  • 提示用户刷新页面,拉取最新 HTML 和资源。
<script onerror="console.error('SRI校验失败,资源被篡改或版本不匹配')" src="/assets/main.js" integrity="xxx" crossorigin="anonymous"></script>

总结

SRI 不是缓存的替代品,而是安全层的加固。在我们整套静态站点体系里:
资源路径白名单(构建拦截非法链接)→ 增量发布 + 图片压缩(体积优化)→ LCP/性能埋点(观测)→ Workbox 离线缓存(可用性)→ 缓存GC(存储清理)→ SRI完整性校验(安全防篡改)。

到这里,一套兼顾体验、性能、离线可用、安全的静态站点链路已经闭环。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。