从本地托管的 Python 应用向 Azure 资源进行身份验证

在 Azure 外部(例如本地或第三方数据中心)托管的应用在访问 Azure 资源时应使用应用程序服务主体向 Azure 进行身份验证。 应用程序服务主体对象是使用 Azure 中的应用注册过程创建的。 创建应用程序服务主体时,将为应用生成客户端 ID 和客户端机密。 接着将客户端 ID、客户端机密和租户 ID 存储在环境变量中,以便 Azure SDK for Python 可将其用于在运行时向 Azure 对应用进行身份验证。

应为托管应用的每个环境创建不同的应用注册。 这允许为每个服务主体配置特定于环境的资源权限,并确保部署到一个环境的应用不会与属于另一个环境的 Azure 资源通信。

1 - 在 Azure 中注册应用程序

可以使用 Azure 门户或 Azure CLI 向 Azure 注册应用。

APP_NAME=<app-name>
az ad sp create-for-rbac --name $APP_NAME

命令的输出类似于以下内容。 记下这些值或使此窗口保持打开状态,因为在后续步骤中需使用这些值,且无法再次查看密码(客户端密码)值。

{
  "appId": "00001111-aaaa-2222-bbbb-3333cccc4444",
  "displayName": "msdocs-python-sdk-auth-prod",
  "password": "Ee5Ff~6Gg7.-Hh8Ii9Jj0Kk1Ll2Mm3_Nn4Oo5Pp6",
  "tenant": "aaaabbbb-0000-cccc-1111-dddd2222eeee"
}

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

APP_ID=$(az ad sp create-for-rbac \
  --name $APP_NAME --query appId --output tsv)

2 - 将角色分配到应用程序服务主体

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

使用 az role assignment create 命令为服务主体分配 Azure 中的角色。

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 角色一文。

3 - 为应用程序配置环境变量

必须为运行 Python 应用的进程设置环境变量 AZURE_CLIENT_IDAZURE_TENANT_IDAZURE_CLIENT_SECRET,以使应用程序服务主体凭据在运行时可供应用使用。 DefaultAzureCredential 对象在这些环境变量中查找服务主体信息。

如下所示,使用 Gunicorn 在 UNIX 服务器环境中运行 Python Web 应用时,可以使用 EnvironmentFile 文件中的 gunicorn.server 指令指定应用的环境变量。

[Unit]
Description=gunicorn daemon
After=network.target  
  
[Service]  
User=www-user
Group=www-data
WorkingDirectory=/path/to/python-app
EnvironmentFile=/path/to/python-app/py-env/app-environment-variables
ExecStart=/path/to/python-app/py-env/gunicorn --config config.py wsgi:app
            
[Install]  
WantedBy=multi-user.target

如下所示,在 EnvironmentFile 指令中指定的文件应包含具有值的环境变量列表。

AZURE_CLIENT_ID=<value>
AZURE_TENANT_ID=<value>
AZURE_CLIENT_SECRET=<value>

4 - 在应用程序中实现 DefaultAzureCredential

若要向 Azure 对 Azure SDK 客户端对象进行身份验证,应用程序应使用 DefaultAzureCredential 包中的 azure.identity 类。

首先将 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)

当上述代码实例化 DefaultAzureCredential 对象时,DefaultAzureCredential 读取环境变量 AZURE_TENANT_IDAZURE_CLIENT_IDAZURE_CLIENT_SECRET,以获取连接到 Azure 的应用程序服务主体信息。