使用 Microsoft.Identity.Web 生成守护进程应用程序和代理身份。

在本文中,你将使用Microsoft.Identity.Web构建守护程序应用程序、后台服务和自主代理。 这些应用程序在没有用户交互的情况下运行,并使用 应用程序标识 (客户端凭据)或 代理标识进行身份验证。

了解支持的方案

Microsoft。Identity.Web 支持三种类型的非交互式应用程序:

情景 身份验证类型 令牌类型 用例
标准守护程序 客户端凭据(机密/证书) 仅限应用的访问令牌 后台服务、计划作业、数据处理
自治代理 使用客户端凭据的代理标识 应用专用的代理访问令牌 Copilot智能助手,以代理身份运作的自动化服务。 (通常位于受保护的 Web API 中)
代理用户标识 代理用户标识 使用客户端凭据的代理用户标识 为代理用户身份执行操作的自治服务。 (通常位于受保护的 Web API 中)

开始

先决条件

在开始之前,请确保具备:

  • .NET 8.0 或更高版本
  • 使用 客户端凭据(客户端机密或证书)Microsoft Entra 应用注册
  • 对于代理场景:Microsoft Entra 租户中配置的代理标识

安装软件包

将所需的 NuGet 包添加到项目:

dotnet add package Microsoft.Identity.Web
dotnet add package Microsoft.Extensions.Hosting

选择配置方法

Microsoft。Identity.Web 提供了两种方法来配置守护程序应用程序:

最适合: 快速原型、控制台应用、测试和简单的守护程序服务。

以下代码创建一个 TokenAcquirerFactory,配置下游 API 和Microsoft Graph,并调用图形 API:

using Microsoft.Identity.Abstractions;
using Microsoft.Identity.Web;

// Get the token acquirer factory instance
var tokenAcquirerFactory = TokenAcquirerFactory.GetDefaultInstance();

// Configure downstream API and Microsoft Graph (optional)
tokenAcquirerFactory.Services.AddDownstreamApis(
    tokenAcquirerFactory.Configuration.GetSection("DownstreamApis"))
    .AddMicrosoftGraph();

var serviceProvider = tokenAcquirerFactory.Build();

// Call Microsoft Graph
var graphClient = serviceProvider.GetRequiredService<GraphServiceClient>();
var users = await graphClient.Users.GetAsync();

优点:

  • 最小样板代码
  • 自动加载 appsettings.json
  • 非常适合简单场景
  • 单行初始化

缺点:

  • 不适合并行运行的测试(单一实例)

最适合: 生产应用程序、复杂方案、依赖项注入、可测试性。

以下代码使用.NET通用主机配置身份验证、令牌获取、缓存和后台服务:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Identity.Web;

var host = Host.CreateDefaultBuilder(args)
    .ConfigureServices((context, services) =>
    {
        // Configure authentication
        services.Configure<MicrosoftIdentityApplicationOptions>(
            context.Configuration.GetSection("AzureAd"));

        // Add token acquisition (true = singleton lifetime)
        services.AddTokenAcquisition(true);

        // Add token cache (in-memory for development)
        services.AddInMemoryTokenCaches();

        // Add HTTP client for API calls
        services.AddHttpClient();

        // Add Microsoft Graph (optional)
        services.AddMicrosoftGraph();

        // Add your background service
        services.AddHostedService<DaemonWorker>();
    })
    .Build();

await host.RunAsync();

优点:

  • 完全控制配置提供程序
  • 使用构造函数注入提高可测试性
  • 与 ASP.NET Core托管模型集成
  • 支持复杂方案(多个身份验证方案)
  • 生产就绪体系结构
  • 支持并行测试执行(每个测试的独立服务提供商)

注释

该参数trueAddTokenAcquisition(true)表示该服务注册为单例(应用程序整个生命周期内的单个实例)。 在 Web 应用程序中,使用 false 作为范围生存期。

建议:TokenAcquirerFactory从原型和单线程测试开始。 在生成生产应用程序或运行并行测试时迁移到完整 ServiceCollection 模式。


配置标准守护程序应用程序

标准守护程序应用程序使用 客户端凭据 (客户端机密或证书)进行身份验证,并获取 仅应用访问令牌 来调用 API。

配置身份验证设置

将以下配置添加到 appsettings.json 文件。 可以使用客户端密码或证书(建议用于生产):

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "your-client-id",

    "ClientSecret": "your-client-secret",

    "ClientCredentials": [
      // Option 1: Client Secret
      {
        "SourceType": "ClientSecret",
        "ClientSecret": "your-client-secret",
      },
      // Option 2: Certificate (recommended for production)
      {
        "SourceType": "StoreWithDistinguishedName",
        "CertificateStorePath": "CurrentUser/My",
        "CertificateDistinguishedName": "CN=DaemonAppCert"
      }
      // More options: https://aka.ms/ms-id-web/client-credentials
    ]
  }
}

重要:appsettings.json设置为复制到输出目录。 将以下内容添加到 .csproj 文件:

<ItemGroup>
  <None Update="appsettings.json">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </None>
</ItemGroup>

ASP.NET Core应用程序自动复制此文件,但守护程序应用(和 OWIN 应用)不会复制。

设置服务配置

以下 Program.cs 代码注册Microsoft标识选项、令牌获取、缓存和托管后台服务:

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Identity.Web;

var host = Host.CreateDefaultBuilder(args)
    .ConfigureServices((context, services) =>
    {
        IConfiguration configuration = context.Configuration;

        // Configure Microsoft Identity options
        services.Configure<MicrosoftIdentityApplicationOptions>(
            configuration.GetSection("AzureAd"));

        // Add token acquisition (true = singleton)
        services.AddTokenAcquisition(true);

        // Add token cache
        services.AddInMemoryTokenCaches(); // For development
        // services.AddDistributedTokenCaches(); // For production

        // Add HTTP client
        services.AddHttpClient();

        // Add Microsoft Graph SDK (optional)
        services.AddMicrosoftGraph();

        // Add your background service
        services.AddHostedService<DaemonWorker>();
    })
    .Build();

await host.RunAsync();

调用 Microsoft Graph

以下 DaemonWorker.cs 类使用 Graph SDK 按定期计划列出用户:

using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Graph;
using Microsoft.Identity.Abstractions;

public class DaemonWorker : BackgroundService
{
    private readonly GraphServiceClient _graphClient;
    private readonly ILogger<DaemonWorker> _logger;

    public DaemonWorker(
        GraphServiceClient graphClient,
        ILogger<DaemonWorker> logger)
    {
        _graphClient = graphClient;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                // Call Microsoft Graph with app-only permissions
                var users = await _graphClient.Users
                    .GetAsync(cancellationToken: stoppingToken);

                _logger.LogInformation($"Found {users?.Value?.Count} users");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error calling Microsoft Graph");
            }

            await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
        }
    }
}

使用 IAuthorizationHeaderProvider

若要更好地控制 HTTP 调用,请使用 IAuthorizationHeaderProvider 手动创建授权标头:

using Microsoft.Identity.Abstractions;

public class DaemonService
{
    private readonly IAuthorizationHeaderProvider _authProvider;
    private readonly HttpClient _httpClient;

    public DaemonService(
        IAuthorizationHeaderProvider authProvider,
        IHttpClientFactory httpClientFactory)
    {
        _authProvider = authProvider;
        _httpClient = httpClientFactory.CreateClient();
    }

    public async Task<string> CallApiAsync()
    {
        // Get authorization header for app-only access
        string authHeader = await _authProvider
            .CreateAuthorizationHeaderForAppAsync(
                scopes: "https://graph.microsoft.com/.default");

        // Add to HTTP request
        _httpClient.DefaultRequestHeaders.Clear();
        _httpClient.DefaultRequestHeaders.Add("Authorization", authHeader);

        var response = await _httpClient.GetStringAsync(
            "https://graph.microsoft.com/v1.0/users");

        return response;
    }
}

另请参阅 调用下游 API 以了解 Microsoft Identity Web 提供的所有调用下游 API 的方法。


配置自治代理(代理标识)

自治代理使用 代理标识 来获取仅限应用的令牌。 此模式适用于Copilot方案和自治服务。

注释

Microsoft 建议代理程序从受保护的 Web API 内部调用下游 API,即使在代理获取应用令牌的情况下也是如此。

配置代理服务

以下代码使用内存中配置设置身份验证、令牌获取和代理标识支持:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Identity.Web;

var services = new ServiceCollection();

// Configuration
var configuration = new ConfigurationBuilder()
    .AddInMemoryCollection(new Dictionary<string, string?>
    {
        ["AzureAd:Instance"] = "https://login.microsoftonline.com/",
        ["AzureAd:TenantId"] = "your-tenant-id",
        ["AzureAd:ClientId"] = "your-agent-app-client-id",
        ["AzureAd:ClientCredentials:0:SourceType"] = "StoreWithDistinguishedName",
        ["AzureAd:ClientCredentials:0:CertificateStorePath"] = "CurrentUser/My",
        ["AzureAd:ClientCredentials:0:CertificateDistinguishedName"] = "CN=YourCert"
    })
    .Build();

services.AddSingleton<IConfiguration>(configuration);

// Configure Microsoft Identity
services.Configure<MicrosoftIdentityApplicationOptions>(
    configuration.GetSection("AzureAd"));

services.AddTokenAcquisition(true);
services.AddInMemoryTokenCaches();
services.AddHttpClient();
services.AddMicrosoftGraph();

// Add agent identities support
services.AddAgentIdentities();

var serviceProvider = services.BuildServiceProvider();

使用代理标识获取令牌

配置代理服务后,使用 IAuthorizationHeaderProvider 或 Microsoft Graph SDK 获取令牌:

using Microsoft.Identity.Abstractions;
using Microsoft.Graph;

// Your agent identity GUID
string agentIdentityId = "d84da24a-2ea2-42b8-b5ab-8637ec208024";

// Option 1: Using IAuthorizationHeaderProvider
IAuthorizationHeaderProvider authProvider =
    serviceProvider.GetRequiredService<IAuthorizationHeaderProvider>();

var options = new AuthorizationHeaderProviderOptions()
    .WithAgentIdentity(agentIdentityId);

string authHeader = await authProvider.CreateAuthorizationHeaderForAppAsync(
    scopes: "https://graph.microsoft.com/.default",
    options);

// Option 2: Using Microsoft Graph SDK
GraphServiceClient graphClient =
    serviceProvider.GetRequiredService<GraphServiceClient>();

var applications = await graphClient.Applications.GetAsync(request =>
{
    request.Options.WithAuthenticationOptions(authOptions =>
    {
        authOptions.WithAgentIdentity(agentIdentityId);
    });
});

查看完整的自治代理示例

以下类将代理标识令牌获取和图形 API调用包装到可重用的服务中:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Graph;
using Microsoft.Identity.Abstractions;
using Microsoft.Identity.Web;

public class AutonomousAgentService
{
    private readonly GraphServiceClient _graphClient;
    private readonly IAuthorizationHeaderProvider _authProvider;
    private readonly string _agentIdentityId;

    public AutonomousAgentService(
        string agentIdentityId,
        IServiceProvider serviceProvider)
    {
        _agentIdentityId = agentIdentityId;
        _graphClient = serviceProvider.GetRequiredService<GraphServiceClient>();
        _authProvider = serviceProvider.GetRequiredService<IAuthorizationHeaderProvider>();
    }

    public async Task<string> GetAuthorizationHeaderAsync()
    {
        var options = new AuthorizationHeaderProviderOptions()
            .WithAgentIdentity(_agentIdentityId);

        return await _authProvider.CreateAuthorizationHeaderForAppAsync(
            "https://graph.microsoft.com/.default",
            options);
    }

    public async Task<IEnumerable<Application>> ListApplicationsAsync()
    {
        var apps = await _graphClient.Applications.GetAsync(request =>
        {
            request.Options.WithAuthenticationOptions(options =>
            {
                options.WithAgentIdentity(_agentIdentityId);
            });
        });

        return apps?.Value ?? Enumerable.Empty<Application>();
    }
}

配置代理用户标识

代理用户标识使代理可以代表具有委派权限的代理用户执行操作。 使用此模式适用于需要自己邮箱或其他用户范围资源的代理。

先决条件

若要使用代理用户标识,需要:

  • 在 Microsoft Entra ID 中注册的代理蓝图
  • 代理标识已创建并链接到代理应用程序
  • 与代理标识关联的代理用户标识

配置代理用户服务

以下代码使用证书凭据配置代理应用程序标识并注册所需的服务:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Identity.Web;
using System.Security.Cryptography.X509Certificates;

var services = new ServiceCollection();

// Configure agent application
services.Configure<MicrosoftIdentityApplicationOptions>(options =>
{
    options.Instance = "https://login.microsoftonline.com/";
    options.TenantId = "your-tenant-id";
    options.ClientId = "your-agent-app-client-id";

    // Use certificate for agent authentication
    options.ClientCredentials = new[]
    {
        CertificateDescription.FromStoreWithDistinguishedName(
            "CN=YourCertificate",
            StoreLocation.CurrentUser,
            StoreName.My)
    };
});

// Add services (true = singleton)
services.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
services.AddTokenAcquisition(true);
services.AddInMemoryTokenCaches();
services.AddHttpClient();
services.AddMicrosoftGraph();
services.AddAgentIdentities();

var serviceProvider = services.BuildServiceProvider();

通过代理身份获取用户令牌

可以通过 UPN 或对象 ID 标识目标用户。

用户名(UPN)

using Microsoft.Identity.Abstractions;
using Microsoft.Graph;

string agentIdentityId = "your-agent-identity-id";
string userUpn = "user@yourtenant.onmicrosoft.com";

// Get authorization header
IAuthorizationHeaderProvider authProvider =
    serviceProvider.GetRequiredService<IAuthorizationHeaderProvider>();

var options = new AuthorizationHeaderProviderOptions()
    .WithAgentUserIdentity(
        agentApplicationId: agentIdentityId,
        username: userUpn);

string authHeader = await authProvider.CreateAuthorizationHeaderForUserAsync(
    scopes: new[] { "https://graph.microsoft.com/.default" },
    options);

// Or use Microsoft Graph SDK
GraphServiceClient graphClient =
    serviceProvider.GetRequiredService<GraphServiceClient>();

var me = await graphClient.Me.GetAsync(request =>
{
    request.Options.WithAuthenticationOptions(options =>
        options.WithAgentUserIdentity(agentIdentityId, userUpn));
});

按用户对象 ID

string agentIdentityId = "your-agent-identity-id";
Guid userObjectId = Guid.Parse("user-object-id");

var options = new AuthorizationHeaderProviderOptions()
    .WithAgentUserIdentity(
        agentApplicationId: agentIdentityId,
        userId: userObjectId);

string authHeader = await authProvider.CreateAuthorizationHeaderForUserAsync(
    scopes: new[] { "https://graph.microsoft.com/.default" },
    options);

// With Graph SDK
var me = await graphClient.Me.GetAsync(request =>
{
    request.Options.WithAuthenticationOptions(options =>
        options.WithAgentUserIdentity(agentIdentityId, userObjectId));
});

用 ClaimsPrincipal 缓存令牌

为了获得更好的性能,请通过传递 ClaimsPrincipal 实例来缓存用户令牌。 第一个调用使用uidutid声明填充主体身份,后续调用重复使用缓存的令牌:

using System.Security.Claims;
using Microsoft.Identity.Abstractions;

// First call - creates cache entry
ClaimsPrincipal userPrincipal = new ClaimsPrincipal();

string authHeader = await authProvider.CreateAuthorizationHeaderForUserAsync(
    scopes: new[] { "https://graph.microsoft.com/.default" },
    options,
    userPrincipal);

// ClaimsPrincipal now has uid and utid claims for caching
bool hasUserId = userPrincipal.HasClaim(c => c.Type == "uid");
bool hasTenantId = userPrincipal.HasClaim(c => c.Type == "utid");

// Subsequent calls - uses cache
authHeader = await authProvider.CreateAuthorizationHeaderForUserAsync(
    scopes: new[] { "https://graph.microsoft.com/.default" },
    options,
    userPrincipal); // Reuse the same principal

覆盖租户

对于多租户场景,可以在运行时覆盖租户。 当应用配置了 "common" ,但需要面向特定租户时,这非常有用:

var options = new AuthorizationHeaderProviderOptions()
    .WithAgentUserIdentity(agentIdentityId, userUpn);

// Override tenant (useful when app is configured with "common")
options.AcquireTokenOptions.Tenant = "specific-tenant-id";

string authHeader = await authProvider.CreateAuthorizationHeaderForUserAsync(
    scopes: new[] { "https://graph.microsoft.com/.default" },
    options);

// With Graph SDK
var me = await graphClient.Me.GetAsync(request =>
{
    request.Options.WithAuthenticationOptions(options =>
    {
        options.WithAgentUserIdentity(agentIdentityId, userUpn);
        options.AcquireTokenOptions.Tenant = "specific-tenant-id";
    });
});

查看完整的代理用户标识示例

以下类提供了使用代理用户标识获取用户配置文件和授权标头的方法:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Graph;
using Microsoft.Identity.Abstractions;
using System.Security.Claims;

public class AgentUserService
{
    private readonly IAuthorizationHeaderProvider _authProvider;
    private readonly GraphServiceClient _graphClient;
    private readonly string _agentIdentityId;

    public AgentUserService(
        string agentIdentityId,
        IServiceProvider serviceProvider)
    {
        _agentIdentityId = agentIdentityId;
        _authProvider = serviceProvider.GetRequiredService<IAuthorizationHeaderProvider>();
        _graphClient = serviceProvider.GetRequiredService<GraphServiceClient>();
    }

    public async Task<User> GetUserProfileAsync(string userUpn)
    {
        var me = await _graphClient.Me.GetAsync(request =>
        {
            request.Options.WithAuthenticationOptions(options =>
                options.WithAgentUserIdentity(_agentIdentityId, userUpn));
        });

        return me!;
    }

    public async Task<User> GetUserProfileByIdAsync(Guid userObjectId)
    {
        var me = await _graphClient.Me.GetAsync(request =>
        {
            request.Options.WithAuthenticationOptions(options =>
                options.WithAgentUserIdentity(_agentIdentityId, userObjectId));
        });

        return me!;
    }

    public async Task<string> GetAuthHeaderForUserAsync(
        string userUpn,
        ClaimsPrincipal? cachedPrincipal = null)
    {
        var options = new AuthorizationHeaderProviderOptions()
            .WithAgentUserIdentity(_agentIdentityId, userUpn);

        return await _authProvider.CreateAuthorizationHeaderForUserAsync(
            scopes: new[] { "https://graph.microsoft.com/.default" },
            options,
            cachedPrincipal ?? new ClaimsPrincipal());
    }
}

创建可重用的服务配置

定义扩展方法

创建可重用的扩展方法,以在整个应用程序中封装代理标识配置:

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.TokenCacheProviders.InMemory;

public static class ServiceCollectionExtensions
{
    public static IServiceProvider ConfigureServicesForAgentIdentities(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        // Add configuration
        services.AddSingleton(configuration);

        // Configure Microsoft Identity options
        services.Configure<MicrosoftIdentityApplicationOptions>(
            configuration.GetSection("AzureAd"));

        services.AddTokenAcquisition(true);

        // Add token caching
        services.AddInMemoryTokenCaches();

        // Add HTTP client
        services.AddHttpClient();

        // Add Microsoft Graph (optional)
        services.AddMicrosoftGraph();

        // Add agent identities support
        services.AddAgentIdentities();

        return services.BuildServiceProvider();
    }
}

使用扩展方法

调用扩展方法以在单个行中配置服务:

var services = new ServiceCollection();
var configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .Build();

var serviceProvider = services.ConfigureServicesForAgentIdentities(configuration);

调用 API

本部分介绍如何使用这三种身份验证模式中的每一种调用 API。

调用 Microsoft Graph

以下示例演示如何将Microsoft Graph作为标准守护程序、自治代理和代理用户标识调用:

using Microsoft.Graph;

GraphServiceClient graphClient =
    serviceProvider.GetRequiredService<GraphServiceClient>();

// Standard daemon (app-only)
var users = await graphClient.Users.GetAsync();

// Autonomous agent (app-only with agent identity)
var apps = await graphClient.Applications.GetAsync(request =>
{
    request.Options.WithAuthenticationOptions(options =>
    {
        options.WithAgentIdentity("agent-identity-id");
        options.RequestAppToken = true;
    });
});

// Agent user identity (delegated with user context)
var me = await graphClient.Me.GetAsync(request =>
{
    request.Options.WithAuthenticationOptions(options =>
        options.WithAgentUserIdentity("agent-identity-id", "user@tenant.com"));
});

使用 IDownstreamApi 调用自定义 API

使用 IDownstreamApi 通过三种身份验证模式之一来调用您自己的受保护 API:

using Microsoft.Identity.Abstractions;

IDownstreamApi downstreamApi =
    serviceProvider.GetRequiredService<IDownstreamApi>();

// Standard daemon
var result = await downstreamApi.GetForAppAsync<ApiResponse>(
    serviceName: "MyApi",
    options => options.RelativePath = "api/data");

// With agent identity
var result = await downstreamApi.GetForAppAsync<ApiResponse>(
    serviceName: "MyApi",
    options =>
    {
        options.RelativePath = "api/data";
        options.WithAgentIdentity("agent-identity-id");
    });

// Agent user identity
var result = await downstreamApi.GetForUserAsync<ApiResponse>(
    serviceName: "MyApi",
    options =>
    {
        options.RelativePath = "api/data";
        options.WithAgentUserIdentity("agent-identity-id", "user@tenant.com");
    });

进行手动 HTTP 调用

需要完全控制 HTTP 请求时直接使用 IAuthorizationHeaderProvider

using Microsoft.Identity.Abstractions;

IAuthorizationHeaderProvider authProvider =
    serviceProvider.GetRequiredService<IAuthorizationHeaderProvider>();

HttpClient httpClient = new HttpClient();

// Standard daemon
string authHeader = await authProvider.CreateAuthorizationHeaderForAppAsync(
    "https://graph.microsoft.com/.default");

httpClient.DefaultRequestHeaders.Add("Authorization", authHeader);
var response = await httpClient.GetStringAsync("https://graph.microsoft.com/v1.0/users");

// With agent identity
var options = new AuthorizationHeaderProviderOptions()
    .WithAgentIdentity("agent-identity-id");

authHeader = await authProvider.CreateAuthorizationHeaderForAppAsync(
    "https://graph.microsoft.com/.default",
    options);

// Agent user identity
var userOptions = new AuthorizationHeaderProviderOptions()
    .WithAgentUserIdentity("agent-identity-id", "user@tenant.com");

authHeader = await authProvider.CreateAuthorizationHeaderForUserAsync(
    new[] { "https://graph.microsoft.com/.default" },
    userOptions);

配置令牌缓存

根据环境选择缓存策略。

开发:内存中缓存

使用内存中缓存进行本地开发和测试:

services.AddInMemoryTokenCaches();

生产:分布式缓存

在生产环境中,请使用分布式缓存来在应用程序重启和扩展实例的过程中持久化令牌。

SQL Server

将令牌存储在SQL Server表中:

services.AddDistributedSqlServerCache(options =>
{
    options.ConnectionString = configuration["ConnectionStrings:TokenCache"];
    options.SchemaName = "dbo";
    options.TableName = "TokenCache";
});
services.AddDistributedTokenCaches();

Redis

使用 Redis 实现高性能分布式令牌缓存:

services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = configuration["Redis:ConnectionString"];
    options.InstanceName = "TokenCache_";
});
services.AddDistributedTokenCaches();

Cosmos DB

使用 Cosmos DB 进行全局分布式令牌缓存:

services.AddCosmosDbTokenCaches(options =>
{
    options.CosmosDbConnectionString = configuration["CosmosDb:ConnectionString"];
    options.DatabaseId = "TokenCache";
    options.ContainerId = "Tokens";
});

了解详细信息:令牌缓存配置


浏览Azure示例

Microsoft提供了演示守护程序应用模式的示例。

示例存储库

active-directory-dotnetcore-daemon-v2

此存储库包含多个方案:

示例 说明 链接
1-Call-MSGraph 使用客户端凭据调用Microsoft Graph的基本守护程序 View 示例
2-Call-OwnApi 调用自己受保护的 Web API 的守护程序 View 示例
3-Using-KeyVault 使用Azure 密钥保管库进行证书存储的守护程序 View 示例
4-多租户 多租户后台应用 View 示例
5-Call-MSGraph-ManagedIdentity 在 Azure 上使用托管标识的守护程序 View 示例

将示例模式与生产模式进行比较

为了简单起见,Azure示例使用 TokenAcquirerFactory.GetDefaultInstance(),这是适用于 simple 控制台应用、原型和测试的建议方法。 本指南显示了这两种模式:

TokenAcquirerFactory Pattern (Azure 示例):

// Simple, perfect for prototypes and tests
var tokenAcquirerFactory = TokenAcquirerFactory.GetDefaultInstance();
tokenAcquirerFactory.Services.AddDownstreamApi("MyApi", ...);
var serviceProvider = tokenAcquirerFactory.Build();

完整 服务集合 模式(生产应用):

// More control, testable, follows DI best practices
var services = new ServiceCollection();
services.AddTokenAcquisition(true); // true = singleton
services.Configure<MicrosoftIdentityApplicationOptions>(...);
var serviceProvider = services.BuildServiceProvider();

何时使用:

  • 使用 TokenAcquirerFactory 用于:控制台应用、快速原型、单元测试、简单守护程序服务
  • 使用 ServiceCollection:适用于生产应用程序、ASP.NET Core 集成、复杂 DI 场景以及涉及 IHostedService 的后台服务

这两种方法都完全受支持,并且已准备好生产。 根据应用程序的复杂性和集成需求进行选择。


解决常见问题

AADSTS700016:找不到应用程序

原因: 无效 ClientId 或应用程序未在租户中注册。

Solution:验证配置中的 ClientId是否与Microsoft Entra应用注册匹配。

AADSTS7000215:客户端密码无效

原因: 客户端密码不正确、已过期或未配置。

Solution:

  • 验证Azure门户中的机密是否与配置匹配
  • 检查机密过期日期
  • 考虑将证书用于生产

AADSTS700027:客户端断言包含无效签名

原因: 证书未找到、过期或私钥不可访问。

Solution:

  • 验证证书是否已安装在正确的证书存储中
  • 验证证书专有名称是否与配置匹配
  • 确保应用程序有权读取私钥
  • 请参阅 证书配置指南

AADSTS650052:应用需要访问一个服务

原因: 未授予所需的 API 权限或缺少管理员同意。

Solution:

  1. 导航到 Azure门户 → 应用注册 → 你的应用 → API 权限
  2. 添加所需的权限(例如,User.Read.All for Microsoft Graph)
  3. 单击“授予管理员同意”按钮

代理标识错误

AADSTS50105:已登录用户未被分配到角色

原因: 代理标识未正确配置或未分配给应用程序。

Solution:

  • 验证代理标识是否存在于Microsoft Entra ID
  • 确保代理标识已链接到应用程序
  • 检查代理身份是否具有所需的权限

获取令牌但权限不正确

原因: 使用代理用户标识但请求应用权限,反之亦然。

Solution:

  • 对于仅限应用的令牌,请使用CreateAuthorizationHeaderForAppAsyncWithAgentIdentity
  • 对于委托令牌:与 CreateAuthorizationHeaderForUserAsync 一起使用WithAgentUserIdentity
  • 确保 API 权限与令牌类型匹配(应用程序与委托)

令牌缓存问题

问题: 令牌未缓存,这会导致每次都必须重新获取。

Solution:

  • 对于代理用户标识:跨调用重复使用同一 ClaimsPrincipal 实例
  • 验证分布式缓存连接(如果使用 Redis/SQL)
  • 启用调试日志记录以查看缓存操作

详细诊断:日志记录和诊断指南