通过将源映射发布到 Azure Artifacts 符号服务器来安全地调试原始代码

若要在 DevTools 中安全地 查看和使用原始开发源代码,而不是 Web 服务器返回的编译、缩小和捆绑的生产代码,请使用 Azure Artifacts 符号服务器提供的源映射。

将源映射直接发布到 Web 服务器会使原始源代码公开可见。 若要避免使原始源代码公开可见,请将源映射发布到 Azure Artifacts 符号服务器。 使用此方法可以在调试生产网站时使用 DevTools 中的源映射,而无需将源映射发布到 Web 服务器。

源映射将编译的生产代码映射到原始开发源文件。 然后,在 DevTools 中,可以查看和使用熟悉的开发源文件,而不是已编译的代码。 若要详细了解源映射和在 DevTools 中使用源映射,请参阅 将已处理的代码映射到原始源代码进行调试

概念

必须在 Azure Artifacts 符号服务器上为源映射编制索引,以便在调试生产网站时,源映射可供 DevTools 使用。

为此,请在编译时将 x_microsoft_symbol_client_key 字符串属性添加到源映射。 此属性包含相应原始源文件的 256 位 SHA-2 哈希 的小写十六进制值。

然后,DevTools 能够计算每个已编译文件的此哈希,并使用哈希从 Azure Artifacts 符号服务器检索正确的源映射。 为了安全地检索源映射,DevTools 使用你提供的个人访问令牌连接到 Azure Artifacts 符号服务器。

步骤 1:为 Azure DevOps 生成个人访问令牌

将源映射到 Azure Artifacts 符号服务器需要 个人访问令牌 (或 PAT) 。 编译代码和发布源映射时,生成系统将使用此 PAT。

在 Azure DevOps 中生成 PAT:

  1. 转到 登录到 Azure DevOps 组织 https://dev.azure.com/{yourorganization}

  2. 在 Azure DevOps 中,转到 “用户设置>”“个人访问令牌”:

    Azure DevOps 中的“用户设置”菜单,其中包含“个人访问令牌”命令

    此时会显示 “个人访问令牌 ”页:

    Azure DevOps 中的“个人访问令牌”页

  3. 单击“ 新建令牌”。 此时会 打开“创建新的个人访问令牌 ”对话框:

    “创建新的个人访问令牌”对话框,其中选择了符号的“读取 & 写入”范围

  4. 在“ 名称 ”文本框中,输入 PAT 的名称,例如“发布源映射”。

  5. “过期” 部分中,输入 PAT 的到期日期。

  6. “范围 ”部分中,单击“ 显示所有范围” 以展开该部分。

  7. 向下滚动到“ 符号 ”部分,然后选中“ 读取 & 写入 ”复选框。

  8. 单击“创建”按钮。 此时将显示 “成功! ”对话框:

    包含要复制的 PAT 的“成功!”对话框

  9. 单击“ 复制到剪贴板 ”按钮以复制 PAT。 请确保复制令牌并将其存储在安全位置。 出于安全原因,不会再次显示它。

若要详细了解 PAT,请参阅 使用个人访问令牌

步骤 2:计算脚本的 SHA-256 哈希并将其追加到源映射

在应用程序生成过程的最后一步,对于要发布的每个源映射,应计算源映射对应的 JavaScript 文件的 SHA-256 哈希,并通过字符串属性将其追加到源映射 x_microsoft_symbol_client_key

生成系统因应用程序而异,因此没有明确的单一方法可以应用此功能。 但下面是一个示例 Webpack 5 插件,如果你正在使用它,则可以将其添加到 Webpack 配置:

// file: webpack.plugin-symbols.js
// Copyright (C) Microsoft Corporation. All rights reserved.
// Licensed under the BSD 3-clause license.

const crypto = require('crypto');

module.exports = class PrepareSourceMapsForSymbolServerPlugin {
  /**
   * @param {import('webpack').Compiler} compiler
   * @returns {void}
   */
  apply(compiler) {
    compiler.hooks.emit.tap('PrepareSourceMapsForSymbolServerPlugin', (compilation) => {
      const files = Object.keys(compilation.assets);
      const sourceMaps = files.filter(v => v.endsWith('.map'));
      const sourceFilesAndMapsNames = sourceMaps.map(mapFileName => {
        const sourceFileName = mapFileName.substring(0, mapFileName.length - 4);
        return {
          sourceFileName,
          mapFileName,
        };
      });
      const sourceFiles = sourceFilesAndMapsNames.map(({ sourceFileName, mapFileName }) => {
        const sourceFile = compilation.assets[sourceFileName];
        const sourceFileBuffer = sourceFile.buffer();
        const hasher = crypto.createHash('sha256');
        hasher.write(sourceFileBuffer);
        const digest = hasher.digest();
        const sourceFileHash = digest.toString('hex');

        const sourceMapAsset = compilation.assets[mapFileName];
        const sourceMapSource = sourceMapAsset.source();
        const sourceMapContents = JSON.parse(sourceMapSource);
        sourceMapContents['x_microsoft_symbol_client_key'] = sourceFileHash;
        const rewrittenSourceMapContents = JSON.stringify(sourceMapContents);
        if (!sourceMapAsset.isBuffer()) {
          // Not a buffer -- write to the _value property
          sourceMapAsset._value = rewrittenSourceMapContents;
        } else {
          sourceMapAsset._valueAsBuffer = Buffer.from(rewrittenSourceMapContents, 'utf-8');
        }

        return {
          sourceFileName,
          mapFileName,
          sourceFileHash,
          sourceMapAsset,
        };
      });
    });
  }
};

然后,可以将插件添加到 plugins 配置文件中的 webpack.config.js 节:

const PrepareSourceMapsForSymbolServerPlugin = require('./webpack.plugin-symbols.js');

// ...

module.exports = (env, args) => {
  const mode = process.env.NODE_ENV || (env && env.NODE_ENV) || 'production';
  return {
    devtool: mode === 'production' ? 'hidden-source-map' : 'inline-source-map',
    resolve: {
      modules: [
        path.resolve('./node_modules'),
      ],
    },
    output: {
      publicPath: '/',
      filename: '[name].bundle.js',
      chunkFilename: '[name].chunk.js',
    },
    plugins: [
        // ... other plugins
        new PrepareSourceMapsForSymbolServerPlugin(),
    ]
  };
};

步骤 3:将源映射发布到 Azure Artifacts 符号服务器

若要将源映射发布到 Azure Artifacts 符号服务器,请使用以下方法之一:

下面介绍了这些方法。

使用 Azure DevOps Pipelines 发布源映射

Azure DevOps 附带管道 PublishSymbols@2 生成任务。 此任务可用于将源映射发布到 Azure Artifacts 符号服务器。

配置此任务时,将 indexableFileFormats 参数设置为 AllSourceMap

使用 发布源映射 symbol.exe

符号服务器团队发布可自动下载的 .NET Core 应用程序 symbol.exe。 若要以编程方式下载 symbol.exe,请使用 GET 符号服务 REST API 中终结点的 方法 Client ,如 Client - Get 中所述。 然后运行以下命令,将源映射发布到 Azure Artifacts 符号服务器:

symbol publish
        -d {root directory containing your source maps}
        -n {a unique name for this job}
        -s {service URL, such as https://artifacts.dev.azure.com/contoso}
        --patAuthEnvVar {name of environment variable containing a PAT}
        --indexableFileFormats SourceMap

参数 -n 必须唯一。 重复的作业名称将被拒绝,包括失败的作业的名称。

另请参阅