← 中文资源

简体中文版本

子资源完整性(SRI)

子资源完整性把加密哈希绑定到脚本或样式表,使浏览器拒绝执行与预期摘要不符的内容,保护从 CDN 加载的代码。

阅读英文原文

概念概述

子资源完整性(SRI)(Subresource Integrity (SRI))指的是:子资源完整性把加密哈希绑定到脚本或样式表,使浏览器拒绝执行与预期摘要不符的内容,保护从 CDN 加载的代码。

从更严格的定义看,Subresource Integrity is a security feature in which a link or script element carries a base64 hash of the expected resource, and the browser verifies the fetched bytes against it before use. 该概念在工程实践中的价值,在于它能帮助团队用统一的语言描述一个具体的质量属性或设计约束,从而在评审与协作中减少歧义。

为什么需要它

It protects pages that load code from a CDN or any third-party origin that could be compromised or silently altered. 它与 Preconnect and DNS-Prefetch Hints、Fetch Priority Signals、Speculative Parsing and Preload Scanner 等概念经常一起出现,用于解决相近的工程问题。理解它的适用场景,有助于在多种备选方案之间做出有依据的取舍,而不是凭直觉选择技术方案。

常见误解

  • SRI protects the resource from modification in transit only
  • integrity is checked again on every subsequent navigation

需要避免的反模式

  • pinning a hash without a build step that updates it on release
  • using SRI on a URL that is rewritten or personalised

实践要点

  1. The integrity attribute holds one or more algorithm-prefixed digests, and the browser blocks the resource when none matches, failing closed rather than falling back to unverified code.
  2. Because the hash must match byte for byte, any CDN transformation such as minification or an injected banner invalidates it, so integrity hashes belong to immutable, versioned URLs.
  3. SRI does not cover the initial HTML document or resources loaded by scripts at runtime, so it complements rather than replaces a strong Content Security Policy.

参考来源

https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity

代码示例

export async function sha256Hex(input: string | Uint8Array): Promise<string> {
  if (typeof input !== "string" && !(input instanceof Uint8Array)) {
    throw new TypeError("input must be a string or Uint8Array");
  }
  const bytes = typeof input === "string" ? new TextEncoder().encode(input) : input;
  const digest = await crypto.subtle.digest("SHA-256", bytes);
  return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
}

export async function verifySha256(input: string | Uint8Array, expectedHex: string): Promise<boolean> {
  if (typeof expectedHex !== "string" || !/^[0-9a-f]{64}$/i.test(expectedHex)) {
    throw new RangeError("expectedHex must contain 64 hexadecimal characters");
  }
  const actual = await sha256Hex(input);
  return actual === expectedHex.toLowerCase();
}