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

生成云应用程序时,开发人员通常需要在本地运行和测试其应用。 即使在本地开发期间,应用程序也必须对它与之交互的任何 Azure 服务进行身份验证。 本文介绍如何配置专用服务主体标识,以便在本地开发期间使用。

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

用于本地开发的专用应用程序服务主体遵循最低特权原则。 它们仅授予应用在开发过程中所需的 Azure 资源的访问权限。 这种限制访问可降低不慎访问其他资源的风险。 它还有助于防止在迁移到生产环境时与权限相关的错误(bug),在这些情况下,更广泛的权限可能会带来问题。

在 Azure 中注册应用程序进行本地开发时:

  • 为每个开发人员创建单独的应用注册:此方法为每个开发人员提供自己的服务主体,避免需要共享凭据并启用更精细的访问控制。
  • 为每个应用程序创建单独的应用注册:此方法可确保每个应用仅具有所需的权限,从而减少潜在的攻击面。

若要在本地开发期间启用身份验证,请使用应用程序服务主体的凭据设置环境变量。 用于 Python 的 Azure SDK 会检测这些变量,并使用这些变量对 Azure 服务的请求进行身份验证。

在 Azure 中注册应用程序

在 Azure 中注册应用时,会创建应用程序服务主体对象。 可以使用 Azure 门户或 Azure CLI 执行此注册。 注册过程在 Microsoft Entra ID 中创建应用注册,并为应用生成服务主体对象。 服务主体对象用于向 Azure 服务验证应用。 应用注册过程还会为应用生成客户端密码(密码)。 此机密用于向 Azure 服务验证应用。 客户端机密永远不会存储在源代码管理中,而是存储在 .env 应用程序目录中的文件中。 在运行时,应用程序读取 .env 该文件,并设置用于 Python 的 Azure SDK 用于对应用进行身份验证的环境变量。

以下步骤演示如何在 Azure 中注册应用并为应用创建服务主体。 Azure CLI 和 Azure 门户都显示了这些步骤。

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

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

SERVICE_PRINCIPAL_NAME=<service-principal-name>
az ad sp create-for-rbac --name $SERVICE_PRINCIPAL_NAME

此命令的输出类似于以下内容。 记下这些值或使此窗口保持打开状态,因为在后续步骤中需使用这些值,且无法再次查看密码(客户端密码)值。 但是,如果需要,可稍后添加新密码,而不会使服务主体或现有密码失效。

{
  "appId": "00001111-aaaa-2222-bbbb-3333cccc4444",
  "displayName": "<service-principal-name>",
  "password": "Ee5Ff~6Gg7.-Hh8Ii9Jj0Kk1Ll2Mm3_Nn4Oo5Pp6",
  "tenant": "aaaabbbb-0000-cccc-1111-dddd2222eeee"
}

接下来,需要获取 appID 该值并将其存储到变量中。 此值用于在本地开发环境中设置环境变量,以便用于 Python 的 Azure SDK 可以使用服务主体向 Azure 进行身份验证。

APP_ID=$(az ad sp list \
  --all \
  --query "[?displayName=='$SERVICE_PRINCIPAL_NAME'].appId | [0]" \
  --output tsv)

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

由于多个开发人员通常处理同一应用程序,因此最好通过 Microsoft Entra 安全组管理权限。 创建一个安全组,其中包含应用在本地开发所需的角色,而不是单独为每个开发人员的服务主体分配角色。 使用安全组具有以下优势:

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

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

GROUP_DISPLAY_NAME="<group-name>"
GROUP_MAIL_NICKNAME="<group-mail-nickname>"
GROUP_DESCRIPTION="<group-description>"
az ad group create \
  --display-name $GROUP_DISPLAY_NAME \
  --mail-nickname $GROUP_MAIL_NICKNAME \
  --description $GROUP_DESCRIPTION

若要将成员添加到组中,需要获取应用程序服务主体的对象 ID(与应用程序 ID 不同)。 使用 az ad sp list 列出可用的服务主体。 --filter 参数命令接受 OData 样式筛选器,并可用于筛选列表,如图所示。 该 --query 参数将列仅限制为那些您感兴趣的列。

SP_OBJECT_ID=$(az ad sp list \
  --filter "startswith(displayName,'$GROUP_DISPLAY_NAME')" \
  --query "[0].id" \
  --output tsv)

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

az ad group member add \
    --group $GROUP_DISPLAY_NAME \
    --member-id $SP_OBJECT_ID

注意

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

将角色分配给应用程序

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

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

RESOURCE_GROUP_NAME=<resource-group-name>
SUBSCRIPTION_ID=$(az account show --query id --output tsv)
ROLE_NAME=<role-name>
az role assignment create \
  --assignee "$APP_ID" \
  --scope "./subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP_NAME" \
  --role "$ROLE_NAME"

![!注意] 若要防止 Git Bash 处理 /subscriptions/...作为文件路径,在参数的字符串 scope 前面追加 ./ 并使用整个字符串的双引号。

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

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

例如,若要允许 appId 为 00001111-aaaa-2222-bbbb-3333cccc4444 的应用程序服务主体在 ID 为 的订阅中对 aaaa0a0a-bb1b-cc2c-dd3d-eeeeee4e4e4e 资源组中的所有存储帐户中的 Azure 存储 blob 容器和数据进行读取、写入和删除访问,你可以使用以下命令将应用程序服务主体分配给存储 Blob 数据参与者角色。

az role assignment create --assignee 00001111-aaaa-2222-bbbb-3333cccc4444 \
    --scope "./subscriptions/aaaa0a0a-bb1b-cc2c-dd3d-eeeeee4e4e4e/resourceGroups/msdocs-python-sdk-auth-example" \
    --role "Storage Blob Data Contributor"

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

设置本地开发环境变量

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=00001111-aaaa-2222-bbbb-3333cccc4444
AZURE_TENANT_ID=aaaabbbb-0000-cccc-1111-dddd2222eeee
AZURE_CLIENT_SECRET=Ee5Ff~6Gg7.-Hh8Ii9Jj0Kk1Ll2Mm3_Nn4Oo5Pp6

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

from dotenv import load_dotenv

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

在应用程序中实现 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. DefaultAzureCredential 模块中导入 azure.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)