适用于 .NET 的 Azure 标识库中的凭据链

Azure 标识库提供凭据—从 Azure Core 库的 TokenCredential 类派生的公共类。 凭据表示从 Microsoft Entra ID 获取访问令牌的独特身份验证流。 这些凭证可以链接在一起,形成要尝试的身份验证机制的有序序列。

链接凭据的工作原理

在运行时,凭证链尝试使用序列的第一个凭据进行身份验证。 如果该凭据无法获取访问令牌,则会尝试序列中的下一个凭据,以此类推,直到成功获取访问令牌。 以下序列图说明了这种行为:

凭据链序列图

为何使用凭据链

链接凭据可提供以下优势:

  • 环境感知:根据应用运行的环境自动选择最合适的凭据。 如果没有它,必须编写如下所示的代码:

    TokenCredential credential;
    
    if (app.Environment.IsProduction() || app.Environment.IsStaging())
    {
        credential = new ManagedIdentityCredential(clientId: userAssignedClientId);
    }
    else
    {
        // local development environment
        credential = new VisualStudioCredential();
    }
    
  • 无缝转换:应用可以在不更改身份验证代码的情况下从本地开发迁移到暂存或生产环境。

  • 改进了复原能力:包括一个回退机制,当前一个凭据无法获取访问令牌时,该机制会移动到下一个凭据。

如何选择链接凭据

凭据链接有两种不同的理念:

DefaultAzureCredential 概述

DefaultAzureCredential 是一个固定的预配置凭据链。 它旨在支持许多环境,以及最常见的身份验证流和开发人员工具。 在图形形式中,基础链如下所示:

DefaultAzureCredential 身份验证流程图

DefaultAzureCredential 尝试凭据的顺序如下。

订单 凭据 说明 默认情况下是否启用?
1 环境 读取环境变量集合,以确定是否为应用配置了应用程序服务主体(应用程序用户)。 如果是,则 DefaultAzureCredential 将使用这些值对访问 Azure 的应用进行身份验证。 此方法最常用于服务器环境,但也可以在进行本地开发时使用。
2 工作负载标识 如果将应用部署到启用了工作负载标识的 Azure 主机,请对该帐户进行身份验证。
3 托管标识 如果应用部署到启用了托管标识的 Azure 主机,请使用该托管标识向 Azure 验证应用。
4 Visual Studio 如果开发人员通过登录到 Visual Studio 向 Azure 进行身份验证,请使用同一帐户向 Azure 验证应用。
5 Azure CLI 如果开发人员使用 Azure CLI az login 的命令向 Azure 进行身份验证,请使用同一帐户向 Azure 验证应用。
6 Azure PowerShell 如果开发人员使用 Azure PowerShell Connect-AzAccount cmdlet 向 Azure 进行身份验证,请使用同一帐户向 Azure 验证应用。
7 Azure 开发人员 CLI 如果开发人员使用 Azure Developer CLI 的 azd auth login 命令向 Azure 进行身份验证,请使用该帐户进行身份验证。
8 交互式浏览器 如果已启用,将通过当前系统的默认浏览器以交互方式对开发人员进行身份验证。

最简单的形式是,可以使用 DefaultAzureCredential 的无参数版本,如下所示:

builder.Services.AddAzureClients(clientBuilder =>
{
    clientBuilder.AddBlobServiceClient(
        new Uri("https://<account-name>.blob.core.windows.net"));

    DefaultAzureCredential credential = new();
    clientBuilder.UseCredential(credential);
});

提示

UseCredential建议在上述代码片段中使用上述代码片段中的方法,以便在 ASP.NET Core 应用中使用。 有关详细信息,请参阅 ASP.NET Core 应用中的用于 .NET 的 Azure SDK。

如何自定义 DefaultAzureCredential

若要从 DefaultAzureCredential 中删除凭据,请在 DefaultAzureCredentialOptions 中使用相应的 Exclude 前缀属性。 例如:

builder.Services.AddAzureClients(clientBuilder =>
{
    clientBuilder.AddBlobServiceClient(
        new Uri("https://<account-name>.blob.core.windows.net"));

    clientBuilder.UseCredential(new DefaultAzureCredential(
        new DefaultAzureCredentialOptions
        {
            ExcludeEnvironmentCredential = true,
            ExcludeWorkloadIdentityCredential = true,
            ManagedIdentityClientId = userAssignedClientId,
        }));
});

在前面的代码示例中,从凭证链中删除了 EnvironmentCredentialWorkloadIdentityCredential。 因此,要尝试的第一个凭据是 ManagedIdentityCredential。 修改后的链如下所示:

DefaultAzureCredential 使用 Excludes 属性

注意

InteractiveBrowserCredential 默认情况下被排除在外,因此未在上图中显示。 若要包含 InteractiveBrowserCredential,请传递给 true 构造函数 DefaultAzureCredential(Boolean) 或将属性 DefaultAzureCredentialOptions.ExcludeInteractiveBrowserCredential 设置为 false

随着更多以 Exclude 为前缀的属性设置为 true(配置了凭据排除),使用 DefaultAzureCredential 的优势逐渐减弱。 在这种情况下,ChainedTokenCredential 是更好的选择,并且需要更少的代码。 为了说明,这两个代码示例的行为方式相同:

credential = new DefaultAzureCredential(
    new DefaultAzureCredentialOptions
    {
        ExcludeEnvironmentCredential = true,
        ExcludeWorkloadIdentityCredential = true,
        ExcludeAzureCliCredential = true,
        ExcludeAzurePowerShellCredential = true,
        ExcludeAzureDeveloperCliCredential = true,
        ManagedIdentityClientId = userAssignedClientId
    });

ChainedTokenCredential 概述

ChainedTokenCredential 是一个空链,可向其添加凭据以满足应用的需求。 例如:

builder.Services.AddAzureClients(clientBuilder =>
{
    clientBuilder.AddBlobServiceClient(
        new Uri("https://<account-name>.blob.core.windows.net"));

    clientBuilder.UseCredential(new ChainedTokenCredential(
        new ManagedIdentityCredential(clientId: userAssignedClientId),
        new VisualStudioCredential()));
});

前面的代码示例创建了一个由两个凭据组成的定制凭据链。 首先尝试 ManagedIdentityCredential 的用户分配的托管标识变体,然后在必要时尝试 VisualStudioCredential。 在图形形式中,链如下所示:

ChainedTokenCredential

提示

为了提高性能,请在 ChainedTokenCredential 中为生产环境优化凭据排序。 应最后添加用于本地开发环境的凭据。

DefaultAzureCredential 的使用指南

DefaultAzureCredential 无疑是开始使用 Azure 标识库的最简单方法,但随之而来的是权衡。 将应用部署到 Azure 后,应了解应用的身份验证要求。 因此,强烈建议从 DefaultAzureCredential 迁移到以下解决方案之一:

  • 特定的 TokenCredential 实现,例如 ManagedIdentityCredential。 有关选项,请参阅派生列表
  • 针对运行应用的 Azure 环境优化的精简版 ChainedTokenCredential 实现。

原因如下:

  • 调试挑战:身份验证失败时,调试和识别违规凭据可能很困难。 必须启用日志记录,才能查看从一个凭据到下一个凭据的进度以及每个凭据的成功/失败状态。 有关详细信息,请参阅调试链接凭据
  • 性能开销:按顺序尝试多个凭据的过程可能会导致性能开销。 例如,在本地开发计算机上运行时,托管标识不可用。 因此,ManagedIdentityCredential 在本地开发环境中总是失败,除非通过其相应的 Exclude 前缀属性明确禁用。
  • 不可预知的行为DefaultAzureCredential检查是否存在某些环境变量。 有可能有人可以在主机上的系统级别添加或修改这些环境变量。 这些更改在全局范围内适用,因此会在该计算机上运行的任何应用中改变 DefaultAzureCredential 在运行时的行为。

调试链接凭据

若要诊断意外问题或了解链接凭据正在执行的操作,请在应用中启用日志记录。 (可选)将日志筛选为仅从 Azure 标识库发出的那些事件。 例如:

using AzureEventSourceListener listener = new((args, message) =>
{
    if (args is { EventSource.Name: "Azure-Identity" })
    {
        Console.WriteLine(message);
    }
}, EventLevel.LogAlways);

为了便于说明,假设使用无参数形式 DefaultAzureCredential 对 Log Analytics 工作区的请求进行身份验证。 应用在本地开发环境中运行,Visual Studio 已向 Azure 帐户进行身份验证。 下次运行应用时,输出中会显示以下相关条目:

DefaultAzureCredential.GetToken invoked. Scopes: [ https://api.loganalytics.io//.default ] ParentRequestId: d7ef15d1-50f8-451d-afeb-6b06297a3342
EnvironmentCredential.GetToken invoked. Scopes: [ https://api.loganalytics.io//.default ] ParentRequestId: d7ef15d1-50f8-451d-afeb-6b06297a3342
EnvironmentCredential.GetToken was unable to retrieve an access token. Scopes: [ https://api.loganalytics.io//.default ] ParentRequestId: d7ef15d1-50f8-451d-afeb-6b06297a3342 Exception: Azure.Identity.CredentialUnavailableException (0x80131500): EnvironmentCredential authentication unavailable. Environment variables are not fully configured. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/environmentcredential/troubleshoot
WorkloadIdentityCredential.GetToken invoked. Scopes: [ https://api.loganalytics.io//.default ] ParentRequestId: d7ef15d1-50f8-451d-afeb-6b06297a3342
WorkloadIdentityCredential.GetToken was unable to retrieve an access token. Scopes: [ https://api.loganalytics.io//.default ] ParentRequestId: d7ef15d1-50f8-451d-afeb-6b06297a3342 Exception: Azure.Identity.CredentialUnavailableException (0x80131500): WorkloadIdentityCredential authentication unavailable. The workload options are not fully configured. See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/workloadidentitycredential/troubleshoot
ManagedIdentityCredential.GetToken invoked. Scopes: [ https://api.loganalytics.io//.default ] ParentRequestId: d7ef15d1-50f8-451d-afeb-6b06297a3342
ManagedIdentityCredential.GetToken was unable to retrieve an access token. Scopes: [ https://api.loganalytics.io//.default ] ParentRequestId: d7ef15d1-50f8-451d-afeb-6b06297a3342 Exception: Azure.Identity.CredentialUnavailableException (0x80131500): ManagedIdentityCredential authentication unavailable. No response received from the managed identity endpoint.
VisualStudioCredential.GetToken invoked. Scopes: [ https://api.loganalytics.io//.default ] ParentRequestId: d7ef15d1-50f8-451d-afeb-6b06297a3342
VisualStudioCredential.GetToken succeeded. Scopes: [ https://api.loganalytics.io//.default ] ParentRequestId: d7ef15d1-50f8-451d-afeb-6b06297a3342 ExpiresOn: 2024-08-13T17:16:50.8023621+00:00
DefaultAzureCredential credential selected: Azure.Identity.VisualStudioCredential
DefaultAzureCredential.GetToken succeeded. Scopes: [ https://api.loganalytics.io//.default ] ParentRequestId: d7ef15d1-50f8-451d-afeb-6b06297a3342 ExpiresOn: 2024-08-13T17:16:50.8023621+00:00

在前面的输出中,请注意:

  • EnvironmentCredentialWorkloadIdentityCredentialManagedIdentityCredential 每个项都未能按该顺序获取 Microsoft Entra 访问令牌。
  • 前缀为 DefaultAzureCredential credential selected: 的条目表示所选的凭据,在本例中为 VisualStudioCredential。 由于 VisualStudioCredential 成功,因此未使用超出它的凭据。