生成云应用程序时,开发人员通常需要在本地运行和测试其应用。 即使在本地开发期间,应用程序也必须对它与之交互的任何 Azure 服务进行身份验证。 本文介绍如何配置专用服务主体标识,以便在本地开发期间使用。
用于本地开发的专用应用程序服务主体通过限制对应用在开发期间所需的 Azure 资源的访问权限,支持最低特权原则。 使用专用应用服务主体可降低意外访问其他资源的风险,并有助于防止在过渡到生产时与权限相关的问题,因为更广泛的权限可能导致问题。
在 Azure 中注册应用程序进行本地开发时,建议执行以下作:
- 为每个开发人员创建单独的应用注册:这为每个开发人员提供自己的服务主体,避免需要共享凭据并启用更精细的访问控制。
- 为每个应用程序创建单独的应用注册:这可确保每个应用仅具有所需的权限,从而减少潜在的攻击面。
若要在本地开发期间启用身份验证,请使用应用程序服务主体的凭据设置环境变量。 用于 Python 的 Azure SDK 会检测这些变量,并使用这些变量对 Azure 服务的请求进行身份验证。
1 - 在 Azure 中注册应用程序
在 Azure 中注册应用时,会创建应用程序服务主体对象。 可以使用 Azure 门户或 Azure CLI 执行此注册。 注册过程在 Microsoft Entra ID(前为 Azure Active Directory)中创建应用注册,并为应用生成服务主体对象。 服务主体对象用于向 Azure 服务验证应用。
应用注册过程还会为应用生成客户端密码(密码)。 此机密用于向 Azure 服务验证应用。 客户端机密永远不会存储在源代码管理中,而是存储在 .env
应用程序目录中的文件中。 应用程序在运行时读取 .env
文件,以设定 Azure SDK for Python 用于对应用进行身份验证的环境变量。
以下步骤演示如何在 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)
2 - 创建用于本地开发的 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 组和组成员身份。
3 - 将角色分配到应用程序
接下来,需要确定应用在哪些资源上需要哪些角色(权限),并将这些角色分配到应用。 在此示例中,角色将分配给在步骤 2 中创建的 Microsoft Entra 组。 可以在资源、资源组或订阅范围分配角色。 此示例演示如何在资源组范围分配角色,因为大多数应用程序将其所有 Azure 资源分组到单个资源组中。
使用 az role assignment create 命令为用户、组或应用程序服务主体分配 Azure 中的角色。 可以使用组的对象 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 角色一文。
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=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")
5 - 在应用程序中实现 DefaultAzureCredential
若要向 Azure 对 Azure SDK 客户端对象进行身份验证,应用程序应使用 DefaultAzureCredential
包中的 azure.identity
类。 在此方案中,DefaultAzureCredential
将检测是否已设置环境变量 AZURE_CLIENT_ID
、AZURE_TENANT_ID
和 AZURE_CLIENT_SECRET
,并读取这些变量,以获取要用于连接到 Azure 的应用程序服务主体信息。
首先将 azure.identity 包添加到应用程序中。
pip install azure-identity
接下来,对于在应用中创建 Azure SDK 客户端对象的任何 Python 代码,你需要:
- 从
DefaultAzureCredential
模块中导入azure.identity
类。 - 创建
DefaultAzureCredential
对象。 - 将
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)