在 Umi 4 项目中实现 SRI(子资源完整性)功能

375 阅读4分钟

在 Umi 4 项目中实现 SRI(子资源完整性)功能

背景

在现代前端开发中,确保网站的资源(如 JavaScript、CSS 文件等)未被篡改是非常重要的。子资源完整性(SRI,Subresource Integrity)是一种机制,允许浏览器验证从外部来源加载的资源是否匹配预期的完整性哈希。如果资源被篡改,浏览器将拒绝加载该资源。通过为静态资源添加 SRI 哈希值和 crossorigin="anonymous" 属性,可以提高应用的安全性。

在 Umi 4 项目中,想要启用 SRI 功能,需要对 Webpack 配置做一定的修改。本文将介绍如何在 Umi 4 项目中配置和实现 SRI 功能。

配置步骤

1. 安装依赖

首先,我们需要安装 webpack-subresource-integrity 插件,这是用于生成和处理 SRI 哈希值的核心插件。

npm install webpack-subresource-integrity --save-dev

2. 修改 Umi 4 的 chainWebpack 配置

Umi 4 提供了通过 chainWebpack 配置来扩展 Webpack 配置的能力。在这个配置中,我们将启用 SubresourceIntegrityPlugin 插件,并确保在生产环境下生效。

// config/config.js 或者 .umirc.js 文件
const SubresourceIntegrityPlugin = require('webpack-subresource-integrity');
const HtmlIntegrityPlugin = require('./plugins/htmlIntegrityPlugin'); // 引入自定义的 HTML 处理插件

export default {
  chainWebpack: (memo) => {
    // 配置输出,设置跨域加载方式
    memo.output.crossOriginLoading('anonymous'); // 设置为 anonymous 以启用跨域支持

    // 添加 SRI 插件
    memo.plugin('subresource-integrity').use(
      new SubresourceIntegrityPlugin({
        hashFuncNames: ['sha256', 'sha384'], // 使用 sha256 和 sha384 哈希算法
        enabled: process.env.NODE_ENV === 'production', // 仅在生产环境启用
      })
    );

    // 生产环境下才启用 HTML 的完整性处理
    if (process.env.NODE_ENV === 'production') {
      memo.plugin('html-integrity').use(HtmlIntegrityPlugin);
    }
  }
};

3. 创建 HTML 文件完整性插件

webpack-subresource-integrity 插件将会为生成的资源文件(例如 JS、CSS)添加 integritycrossorigin 属性,但它不直接修改 HTML 文件,因此我们需要自定义一个插件来处理生成的 HTML 文件,手动插入 SRI 哈希。

创建 htmlIntegrityPlugin.js 文件:

// plugins/htmlIntegrityPlugin.js
const fs = require('fs');
const path = require('path');

class HtmlIntegrityPlugin {
  apply(compiler) {
    compiler.hooks.done.tap('HtmlIntegrityPlugin', (stats) => {
      setTimeout(() => {
        const assets = stats.toJson().assets || [];
        const integrityMap = assets.reduce((acc, asset) => {
          if (asset.integrity) {
            acc[asset.name] = asset.integrity; // 保存每个资源的完整性哈希
          }
          return acc;
        }, {});

        const outputPath = compiler.options.output.path;
        const htmlFilePath = path.join(outputPath, '../dist/index.html'); // 获取生成的 HTML 文件路径

        if (fs.existsSync(htmlFilePath)) {
          let html = fs.readFileSync(htmlFilePath, 'utf-8');

          // 遍历资源,替换 HTML 中的标签,添加 integrity 和 crossorigin 属性
          Object.entries(integrityMap).forEach(([asset, integrity]) => {
            const escapedAsset = asset.replace(/[.*+?^${}()|[]\]/g, '\$&');
            const assetName = asset.startsWith('/') ? asset : `/${asset}`;

            if (asset.endsWith('.js')) {
              const regex = new RegExp(`<script\s+[^>]*src=["']/${escapedAsset}["'][^>]*>`, 'g');
              html = html.replace(
                regex,
                `<script src="${assetName}" integrity="${integrity}" crossorigin="anonymous"></script>`
              );
            }

            if (asset.endsWith('.css')) {
              const regex = new RegExp(`<link\s+[^>]*href=["']/${escapedAsset}["'][^>]*>`, 'g');
              html = html.replace(
                regex,
                `<link rel="stylesheet" href="${assetName}" integrity="${integrity}" crossorigin="anonymous">`
              );
            }
          });

          // 将修改后的 HTML 文件写回
          fs.writeFileSync(htmlFilePath, html, 'utf-8');
        }
      }, 10000); // 使用 setTimeout 确保在 Webpack 构建完成后再修改 HTML
    });
  }
}

module.exports = HtmlIntegrityPlugin;

4. 配置生产环境打包(可省略)

确保在生产环境中启用 SRI 功能,因为开发环境通常不会生成完整性哈希值。

.umirc.jsconfig/config.js 中,设置环境变量,并确保 SRI 插件和自定义插件仅在生产环境下启用。

// 在 config/config.js 中
export default {
  define: {
    'process.env.NODE_ENV': process.env.NODE_ENV || 'development', // 确保 NODE_ENV 正确设置
  },
  chainWebpack: (memo) => {
    // 前面提到的 chainWebpack 配置
  }
};

5. 测试 SRI 功能

在完成上述配置后,执行构建并检查生成的 HTML 文件,确保所有的资源标签(如 <script><link>)都有 integritycrossorigin="anonymous" 属性。

npm run build

检查生成的 index.html,确保 JS 和 CSS 文件的 <script><link> 标签包含了 integritycrossorigin 属性。

示例 HTML 片段

<script src="/umi.a813823b.js" integrity="sha256-abc123..." crossorigin="anonymous"></script>
<link href="/umi.a813823b.css" rel="stylesheet" integrity="sha256-xyz789..." crossorigin="anonymous">

总结

通过在 Umi 4 项目中配置 webpack-subresource-integrity 插件并创建一个自定义的 HTML 处理插件,我们可以为生成的静态资源添加 SRI 校验功能,从而提高应用的安全性。这种方法确保了外部资源未被篡改,符合现代 Web 安全最佳实践。

在实施过程中,注意以下几点:

  1. 正确配置 crossOriginLoading:确保资源加载时支持跨域。
  2. 在生产环境中启用 SRI:开发环境通常不需要启用 SRI。
  3. 自定义插件处理 HTML:Webpack 默认不会修改 HTML 文件,需要通过自定义插件实现对 HTML 中 <script><link> 标签的处理。
  4. 注意文件输出路径和标签正则匹配:确保资源路径正确匹配,并适当使用 setTimeout 等延时方法确保文件已经生成完毕。

通过这些配置,您可以在 Umi 4 项目中顺利实现 SRI 功能,从而增强应用的安全性和可靠性。

相关issues:github.com/umijs/umi/i…