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

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

显示本地开发人员中运行的应用如何从 .env 文件获取应用程序服务主体,然后使用该标识连接到 Azure 资源的关系图。

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

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

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

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

1 - 在 Azure 中注册应用程序

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

Azure CLI 命令可以在 Azure Cloud Shell 中或是安装了 Azure CLI 的工作站上运行。

首先,使用 az ad sp create-for-rbac 命令为应用创建新的服务主体。 该命令还会同时为应用创建应用注册。

az ad sp create-for-rbac --name {service-principal-name}

此命令的输出如下所示。 记下这些值或使此窗口保持打开状态,因为后续步骤中需要这些值,并且无法再次查看密码(客户端密码)值。 但是,以后可以添加新密码,而无需根据需要使服务主体或现有密码失效。

{
  "appId": "00000000-0000-0000-0000-000000000000",
  "displayName": "{service-principal-name}",
  "password": "abcdefghijklmnopqrstuvwxyz",
  "tenant": "33333333-3333-3333-3333-333333333333"
}

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

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

  • 由于角色是在组级别分配的,因此可以确保为每个开发人员分配相同的角色。
  • 如果应用需要新角色,则只需将其添加到应用的 Microsoft Entra 组。
  • 如果有新的开发人员加入团队,请为该开发人员创建一个新的应用程序服务主体并将其添加到该组中,以确保开发人员拥有正确的权限来处理应用。

az ad group create 命令用于在 Microsoft Entra ID 中创建安全组。 --display-name--main-nickname 参数是必需的。 为组指定的名称应该基于应用程序的名称。 在组的名称中包含类似于“local-dev”的短语来指示组的用途也很有用。

az ad group create \
    --display-name MyDisplay \
    --mail-nickname MyDisplay  \
    --description "<group-description>"

复制命令输出中属性的值 id 。 这是组的对象 ID。 稍后的步骤需要用到它。 还可以使用 az ad group show 命令来检索此属性。

若要将成员添加到组,需要应用程序服务主体的对象 ID,这不同于应用程序 ID。 使用 az ad sp list 列出可用的服务主体。 --filter 参数命令接受 OData 样式筛选器,并可用于筛选列表,如图所示。 --query 参数将列限制为相关的列。

az ad sp list \
    --filter "startswith(displayName, 'msdocs')" \
    --query "[].{objectId:id, displayName:displayName}" \
    --output table

然后可以使用 az ad group member add 命令将成员添加到组中。

az ad group member add \
    --group <group-name> \
    --member-id <object-id>

注意

默认情况下,创建 Microsoft Entra 安全组仅限于目录中的某些特权角色。 如果无法创建组,请联系目录的管理员。 如果无法将成员添加到现有组,请联系组所有者或目录管理员。 若要了解详细信息,请参阅 “管理 Microsoft Entra 组和组成员身份”。

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

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

使用 az role assignment create 命令在 Azure 中为用户、组或应用程序服务主体分配角色。 可以使用其对象 ID 指定组。 可以使用其 appId 指定应用程序服务主体。

az role assignment create --assignee {appId or objectId} \
    --scope /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName} \
    --role "{roleName}" 

若要获取可分配的角色名称,请使用 az role definition list 命令。

az role definition list \
    --query "sort_by([].{roleName:roleName, description:description}, &roleName)" \
    --output table

例如,若要允许应用程序服务主体使用 appId 00000000-0000-0000-0000-000000000000 读取、写入和删除对订阅中 msdocs-python-sdk-auth-example 资源组11111111-1111-1111-1111-111111111111中所有存储帐户中Azure 存储 blob 容器和数据的访问权限,请使用以下命令将应用程序服务主体分配到 存储 Blob 数据参与者角色。

az role assignment create --assignee 00000000-0000-0000-0000-000000000000 \
    --scope /subscriptions/11111111-1111-1111-1111-111111111111/resourceGroups/msdocs-python-sdk-auth-example \
    --role "Storage Blob Data Contributor"

有关使用 Azure CLI 在资源或订阅级别分配权限的信息,请参阅使用 Azure CLI 分配 Azure 角色一文。

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

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

该文件.env永远不会检查到源代码管理中,因为它包含 Azure 的应用程序密钥。 Python 的标准 .gitignore 文件自动从 检查 中排除.env该文件。

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

pip install python-dotenv

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

  • AZURE_CLIENT_ID → 应用 ID 值。
  • AZURE_TENANT_ID → 租户 ID 值。
  • AZURE_CLIENT_SECRET → 为应用生成的密码/凭据。
AZURE_CLIENT_ID=00000000-0000-0000-0000-000000000000
AZURE_TENANT_ID=11111111-1111-1111-1111-111111111111
AZURE_CLIENT_SECRET=abcdefghijklmnopqrstuvwxyz

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

from dotenv import load_dotenv

if ( os.environ['ENVIRONMENT'] == 'development'):
    print("Loading environment variables from .env file")
    load_dotenv(".env")

5 - 在应用程序中实现 DefaultAzureCredential

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

首先将 azure.identity 包添加到应用程序。

pip install azure-identity

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

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

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

from azure.identity import DefaultAzureCredential
from azure.storage.blob import BlobServiceClient

# Acquire a credential object
token_credential = DefaultAzureCredential()

blob_service_client = BlobServiceClient(
        account_url="https://<my_account_name>.blob.core.windows.net",
        credential=token_credential)