使用服务主体在本地开发期间向 Azure 服务验证 JavaScript 应用的身份

创建云应用程序时,开发人员需要在其本地工作站上调试和测试应用程序。 在本地开发期间,当应用程序在开发人员的工作站上运行时,它仍然必须向应用使用的任何 Azure 服务进行身份验证。 本文介绍如何设置要在本地开发期间使用的专用应用程序服务主体对象。

显示本地开发期间 JavaScript 应用如何使用开发人员的凭据通过在本地安装的开发工具获取这些凭据来连接到 Azure 的关系图。

使用用于本地开发的专用应用程序服务主体可以在应用开发期间遵循最低特权原则。 由于权限的范围严格限定为开发期间应用所需的权限,因此可以防止应用代码意外访问仅供其他应用使用的 Azure 资源。 此方法还可防止在将应用移动到生产环境时发生 bug,因为该应用在开发环境中过度使用。

在 Azure 中注册应用时,将为该应用设置应用程序服务主体。 为本地开发注册应用时,建议:

  • 为每个处理该应用的开发人员单独创建应用注册。 此方法为每个开发人员创建单独的应用程序服务主体,以便在本地开发期间使用,并避免开发人员需要为单个应用程序服务主体共享凭据。
  • 为每个应用单独创建应用注册。 这会将应用的权限范围限定为该应用所需的权限。

在本地开发期间,将使用应用程序服务主体的标识设置环境变量。 用于 JavaScript 的 Azure SDK 读取这些环境变量,并使用此信息向所需的 Azure 资源对应用进行身份验证。

1 - 在 Azure 中注册应用程序

应用程序服务主体对象是使用 Azure 中的应用注册创建的。 可以使用 Azure 门户 或 Azure CLI 创建服务主体。

登录到 Azure 门户并执行以下步骤。

说明 屏幕快照
在 Azure 门户中:
  1. 在 Azure 门户顶部的搜索栏中输入“应用注册”。
  2. 在搜索栏下方显示的菜单中的“服务”标题下,选择标有“应用注册”的项。
显示如何使用 Azure 门户中的顶部搜索栏查找并导航到“应用注册”页的屏幕截图。
在“应用注册”页上,选择“+ 新建注册”。 显示“新建注册”按钮在“应用注册”页中的位置的屏幕截图。
在“注册应用程序”页上,按如下所示填写窗体。
  1. 名称 → 输入该应用注册在 Azure 中的名称。 建议在此名称中包含应用名称、应用注册所针对的用户,以及类似于“dev”的标识符,以指示此应用注册用于本地开发。
  2. 支持的帐户类型 → 仅限此组织目录中的帐户。
选择“注册”以注册应用并创建应用程序服务主体。
显示如何填写“注册应用程序”页的屏幕截图:为应用命名,并将“支持的帐户类型”指定为“仅限此组织目录中的帐户”。
在应用的“应用注册”页上:
  1. 应用程序(客户端)ID → 这是在本地开发期间由应用用来访问 Azure 的应用 ID。 将此值复制到文本编辑器中的临时位置,以便在以后的步骤中使用它。
  2. 目录(租户) ID → 应用在向 Azure 进行身份验证时也需要此值。 将此值复制到文本编辑器中的临时位置,因为在稍后的步骤中也需要用到。
  3. 客户端凭据 → 必须先为应用设置客户端凭据,然后应用才能向 Azure 进行身份验证并使用 Azure 服务。 选择“添加证书或机密”以添加应用的凭据。
完成应用注册后的屏幕截图,其中包含应用程序 ID、租户 ID 的位置。
在“证书和机密”页上,选择“+ 新建客户端密码”。 显示“证书和机密”页上用于创建新客户端机密的链接的位置的屏幕截图。
页面右侧会弹出“添加客户端机密”对话框。 在此对话框中:
  1. 说明 → 输入值“当前”。
  2. 过期时间 → 选择值“24 个月”。
选择“添加”以添加机密。
显示为应用注册过程创建的应用程序服务主体添加了新客户端机密的页面的屏幕截图。
在“证书和机密”页上,你将看到客户端密码的值。

将此值复制到文本编辑器中的临时位置,以便在以后的步骤中使用它。

重要提示:此值只显示这一次。退出或刷新此页面后,将无法再次看到此值。 你可以添加更多客户端机密而不会使这个客户端机密失效,但是你不会再看到这个值。
显示包含生成的客户端机密的页面的屏幕截图。

2 - 创建用于本地开发的 Microsoft Entra 安全组

由于通常有多个开发人员在应用程序中工作,因此建议创建一个Microsoft Entra 组来封装应用在本地开发中所需的角色(权限),而不是将角色分配给单个服务主体对象。 这种做法的优势如下。

  • 由于角色是在组级别分配的,因此可以确保为每个开发人员分配相同的角色。
  • 如果应用需要新角色,则只需将其添加到应用的 Microsoft Entra 组即可。
  • 如果有新的开发人员加入团队,请为该开发人员创建一个新的应用程序服务主体并将其添加到该组中,以确保开发人员拥有正确的权限来处理应用。
说明 屏幕快照
通过在页面顶部的搜索框中键入 Microsoft Entra ID,然后从“服务”下选择“Microsoft Entra ID”,导航到 Azure 门户中的“Microsoft Entra ID”页。 显示如何使用 Azure 门户顶部的搜索栏搜索并导航到 Microsoft Entra ID 页面的屏幕截图。
在“Microsoft Entra ID”页上,从左侧菜单中选择“组”。 显示“组”菜单项在“Microsoft Entra ID 默认目录”页左侧菜单中的位置的屏幕截图。
在“所有组”页上,选择“新建组”。 显示“新建组”按钮在“所有组”页中的位置的屏幕截图。
在“新建组”页上
  1. 组类型 → 安全性
  2. 组名称 → 安全组的名称,通常是基于应用程序名称创建的。 在组的名称中包含类似于 local-dev 的字符串来指示组的用途也很有帮助。
  3. 组说明 → 组的用途说明。
  4. 在“成员”下选择“未选择成员”链接,以将成员添加到组中。
此屏幕截图显示如何为应用程序创建新的 Microsoft Entra 组。
在“添加成员”对话框中
  1. 使用搜索框筛选列表中的主体名称。
  2. 为此应用选择用于本地开发的应用程序服务主体。 选择对象后,它们将会灰显并移至对话框底部的“选定项”列表中。
  3. 完成后,选择“选择”按钮。
“添加成员”对话框的屏幕截图,其中显示了如何选择要包含在组中的应用程序服务主体。
返回“新建组”页,选择“创建”以创建组。

随后会创建组,你将返回到“所有组”页。 最长可能需要在 30 秒后才会显示该组,由于 Azure 门户中会进行缓存,你可能需要刷新页面。
“新建组”页的屏幕截图,其中显示了如何选择“创建”按钮来完成该过程。

3 - 将角色分配到应用程序

接下来,需要确定应用在哪些资源上需要哪些角色(权限),并将这些角色分配到应用。 在此示例中,角色将分配给在步骤 2 中创建的 Microsoft Entra 组。 可以在资源、资源组或订阅范围分配角色。 此示例演示如何在资源组范围分配角色,因为大多数应用程序将其所有 Azure 资源分组到单个资源组中。

说明 屏幕快照
使用 Azure 门户顶部的搜索框搜索资源组名称,找到应用程序的资源组。

在对话框中的“资源组”标题下选择资源组名称,导航到该资源组。
显示如何使用 Azure 门户中的顶部搜索框来查找并导航到要为其分配角色(权限)的资源组的屏幕截图。
在资源组的页面上,从左侧菜单中选择“访问控制(IAM)”。 资源组页面的屏幕截图,其中显示了“访问控制(IAM)”菜单项的位置。
在“访问控制(IAM)”页上
  1. 选择“角色分配”选项卡。
  2. 从顶部菜单中选择“+ 添加”,然后从出现的下拉菜单中选择“添加角色分配”。
显示如何导航到角色分配选项卡,以及用于将角色分配添加到资源组的按钮的位置的屏幕截图。
“添加角色分配”页列出了可为资源组分配的所有角色。
  1. 使用搜索框筛选列表,以便以更容易操作的大小显示内容。 此示例演示如何筛选存储 Blob 角色。
  2. 选择要分配的角色。
    选择“下一步”转到下一屏幕。
显示如何筛选和选择要添加到资源组的角色分配的屏幕截图。
在下一个“添加角色分配”页面中,可以指定要将角色分配给哪个用户。
  1. 在“将访问权限分配给”下选择“用户、组或服务主体”。
  2. 在“成员”下选择“+ 选择成员”
Azure 门户的右侧会打开一个对话框。
该屏幕截图显示了用于选择将角色分配到 Microsoft Entra 组的单选按钮,以及用于选择要将角色分配到的组的链接。
在“选择成员”对话框中
  1. “选择”文本框可用于筛选订阅中的用户和组列表。 如果需要,请键入为应用创建的本地开发 Microsoft Entra 组的前几个字符。
  2. 选择与应用程序关联的本地开发 Microsoft Entra 组。
在对话框底部选择“选择”以继续。
显示如何在“选择成员”对话框中为应用程序筛选和选择 Microsoft Entra 组的屏幕截图。
现在,该 Microsoft Entra 组将在“添加角色分配”屏幕上显示为选中状态。

选择“查看 + 分配”转到最后一页,然后再次选择“查看 + 分配”完成该过程。
显示已完成的“添加角色分配”页,以及用于完成该过程的“查看 + 分配”按钮的位置的屏幕截图。

4 - 设置本地开发环境环境

DefaultAzureCredential 对象在运行时查找一组环境变量中的服务主体信息。 由于大多数开发人员处理多个应用程序,因此建议使用类似于 dotenv 的包.env开发期间存储在应用程序目录中的文件访问环境。 这会限定用于向 Azure 对应用程序进行身份验证的环境变量的范围,以便它们只能由此应用程序使用。

从未将 .env 文件签入源控制,因为它包含 Azure 的应用程序密钥。 JavaScript 的标准 .gitignore 文件会自动从签入中排除 .env 该文件。

若要使用该 dotenv 包,请先在应用程序中安装该包。

npm install dotenv

然后,在应用程序根目录中创建 .env 文件。 如下所示,使用从应用注册进程获取的值设置环境变量值:

  • AZURE_CLIENT_ID → 应用 ID 值。
  • AZURE_TENANT_ID → 租户 ID 值。
  • AZURE_CLIENT_SECRET → 为应用生成的密码/凭据。
AZURE_CLIENT_ID=00001111-aaaa-2222-bbbb-3333cccc4444
AZURE_TENANT_ID=ffffaaaa-5555-bbbb-6666-cccc7777dddd
AZURE_CLIENT_SECRET=Aa1Bb~2Cc3.-Dd4Ee5Ff6Gg7Hh8Ii9_Jj0Kk1Ll2

最后,在应用程序的启动代码中,使用 dotenv 库在启动时从 .env 文件中读取环境变量。

import 'dotenv/config'

5 - 在应用程序中实现 DefaultAzureCredential

若要向 Azure 对 Azure SDK 客户端对象进行身份验证,应用程序应使用 @azure/identity 包中的 DefaultAzureCredential 类。 在此方案中, DefaultAzureCredential 检测环境变量 AZURE_CLIENT_IDAZURE_TENANT_IDAZURE_CLIENT_SECRET 设置并读取这些变量,以获取连接到 Azure 的应用程序服务主体信息。

首先将 @azure/标识 包添加到应用程序。

npm install @azure/identity

接下来,对于在应用中创建 Azure SDK 客户端对象的任何 JavaScript 代码,你需要:

  1. @azure/identity 模块中导入 DefaultAzureCredential 类。
  2. 创建 DefaultAzureCredential 对象。
  3. DefaultAzureCredential 对象传递给 Azure SDK 客户端对象构造函数。

以下代码片段中显示了此操作的示例。

// Azure authentication dependency
import { DefaultAzureCredential } from '@azure/identity';

// Azure resource management dependency
import { SubscriptionClient } from "@azure/arm-subscriptions";

// Acquire credential
const tokenCredential = new DefaultAzureCredential();

async function listSubscriptions() {
  try {

    // use credential to authenticate with Azure SDKs
    const client = new SubscriptionClient(tokenCredential);

    // get details of each subscription
    for await (const item of client.subscriptions.list()) {
      const subscriptionDetails = await client.subscriptions.get(
        item.subscriptionId
      );
      /* 
        Each item looks like:
      
        {
          id: '/subscriptions/aaaa0a0a-bb1b-cc2c-dd3d-eeeeee4e4e4e',
          subscriptionId: 'aaaa0a0a-bb1b-cc2c-dd3d-eeeeee4e4e4e',
          displayName: 'YOUR-SUBSCRIPTION-NAME',
          state: 'Enabled',
          subscriptionPolicies: {
            locationPlacementId: 'Internal_2014-09-01',
            quotaId: 'Internal_2014-09-01',
            spendingLimit: 'Off'
          },
          authorizationSource: 'RoleBased'
        },
    */
      console.log(subscriptionDetails);
    }
  } catch (err) {
    console.error(JSON.stringify(err));
  }
}

listSubscriptions()
  .then(() => {
    console.log("done");
  })
  .catch((ex) => {
    console.log(ex);
  });

DefaultAzureCredential 将自动检测为应用配置的身份验证机制,并获取必要的令牌,以便向 Azure 对应用进行身份验证。 如果应用程序使用多个 SDK 客户端,则同一凭据对象可与每个 SDK 客户端对象一起使用。