Skip to content

子资源完整性(SRI)

子资源完整性(Subresource Integrity, SRI)是一种安全机制,用于确保从 CDN 加载的 JavaScript 和 CSS 文件未被篡改。通过在 <script><link> 标签上添加 integrity 属性,浏览器会验证文件内容的哈希值是否匹配,防止恶意代码通过 CDN 投毒攻击用户。

前置知识

阅读本节前,建议先了解:表单安全

为什么需要 SRI

当使用第三方 CDN 加载 JavaScript 和 CSS 时,存在以下风险:

  1. CDN 被入侵:攻击者篡改 CDN 上的文件内容
  2. 中间人攻击:通过 DNS 劫持等手段替换 CDN 上的文件
  3. CDN 宕机:虽然 SRI 不解决此问题,但配合 fallback 可用

SRI 工作原理

1. 开发者计算 JS/CSS 文件的哈希值
2. 在 script/link 标签上添加 integrity 属性
3. 浏览器下载文件后计算哈希
4. 对比两个哈希值是否一致
5. 不一致则拒绝执行文件

integrity 属性

基本用法

html
<!-- 使用 SRI 的 script 标签 -->
<script
  src="https://cdn.example.com/lib.js"
  integrity="sha384-abc123..."
  crossorigin="anonymous"
></script>

<!-- 使用 SRI 的 link 标签 -->
<link
  rel="stylesheet"
  href="https://cdn.example.com/styles.css"
  integrity="sha384-xyz789..."
  crossorigin="anonymous"
>

crossorigin 属性

crossorigin 属性必须配合 SRI 使用:

html
<!-- crossorigin="anonymous":发送不带凭据的跨域请求 -->
<script
  src="https://cdn.example.com/lib.js"
  integrity="sha384-abc123"
  crossorigin="anonymous"
></script>

crossorigin 是必需的

如果使用 SRI 但不设置 crossorigin,浏览器不会验证文件完整性,SRI 将被忽略。

生成 SRI Hash

命令行生成

bash
# 使用 OpenSSL 生成 SHA-384 哈希
openssl dgst -sha384 -binary lib.js | openssl base64 -A

# 使用 sri-tool
npx sri-tool https://cdn.example.com/lib.js

# 使用 curl + shasum
curl -s https://cdn.example.com/lib.js | shasum -a 384 -b | awk '{print $1}'

在线生成

哈希算法选择

算法说明推荐度
sha384384 位 SHA 哈希推荐
sha512512 位 SHA 哈希更安全
sha256256 位 SHA 哈希兼容性最好

Fallback 策略

当 CDN 资源验证失败时,提供本地备用:

html
<!-- SRI 验证失败时回退到本地文件 -->
<script src="https://cdn.example.com/lib.js"
  integrity="sha384-abc123"
  crossorigin="anonymous"></script>
<script>
  // 检测外部库是否加载成功
  if (typeof MyLibrary === 'undefined') {
    // 回退到本地文件
    var s = document.createElement('script');
    s.src = '/local/lib.js';
    document.head.appendChild(s);
  }
</script>

自动化 SRI 生成

javascript
// Node.js 生成 SRI Hash
const crypto = require('crypto');
const fs = require('fs');

function generateSRIHash(filePath, algorithm = 'sha384') {
  const content = fs.readFileSync(filePath);
  const hash = crypto.createHash(algorithm).update(content).digest('base64');
  return `${algorithm}-${hash}`;
}

// 使用
const hash = generateSRIHash('./lib.min.js');
console.log(hash); // sha384-abc123...

完整示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>SRI 安全示例</title>

  <!-- 带有 SRI 的 CSS -->
  <link
    rel="stylesheet"
    href="https://cdn.example.com/bootstrap/5.3.0/css/bootstrap.min.css"
    integrity="sha384-abc123def456..."
    crossorigin="anonymous"
  />

  <!-- CSP 同时保护 -->
  <meta http-equiv="Content-Security-Policy" content="
    default-src 'self';
    script-src 'self' https://cdn.example.com;
    style-src 'self' https://cdn.example.com;
  ">
</head>
<body>
  <h1>SRI 安全示例</h1>

  <!-- 带有 SRI 的 JavaScript -->
  <script
    src="https://cdn.example.com/jquery/3.7.1/jquery.min.js"
    integrity="sha384-xyz789abc012..."
    crossorigin="anonymous"
  ></script>

  <script
    src="https://cdn.example.com/bootstrap/5.3.0/js/bootstrap.bundle.min.js"
    integrity="sha384-def456abc789..."
    crossorigin="anonymous"
  ></script>

  <!-- 本地脚本不需要 SRI -->
  <script defer src="/js/app.js"></script>
</body>
</html>

注意事项

  1. crossorigin 是必需的:不设置 crossorigin 会使 SRI 失效
  2. SRI 仅适用于外部资源:本地文件不需要 SRI
  3. 哈希算法一致性:integrity 中声明的算法必须与实际计算使用的一致
  4. 文件更新时重新生成哈希:文件内容变化后需要重新计算 integrity 值
  5. SRI 不解决 CDN 宕机问题:需要配合 fallback 策略

最佳实践

  • 对所有 CDN 加载的 JS 和 CSS 使用 SRI
  • 使用 sha384 或 sha512 算法
  • 始终配合 crossorigin="anonymous" 使用
  • 文件更新后重新生成 integrity 哈希
  • 提供 CDN 失效时的 fallback 方案
  • 将 SRI 生成集成到构建流程
  • 配合 CSP 进一步加强安全

下一节

继续学习:Permissions Policy

参考链接