用 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 Generic Host 來配置認證、憑證擷取、快取及背景服務:

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) 的參數表示服務被註冊為單例(應用程式生命週期中的單一實例)。 請使用 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 建議,即使代理取得應用程式令牌,呼叫下游 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));
});

依使用者物件識別碼

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 claims;後續呼叫則重複使用快取的標記:

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-呼叫-MSGraph 基本守護進程呼叫 Microsoft Graph 並提供用戶端憑證 查看範例
2-Call-OwnApi 背景服務呼叫您自己的受保護的 Web API 查看範例
3-Using-KeyVault 使用 Azure Key Vault 儲存憑證的 Daemon 查看範例
4-多租戶 多租戶守護程序應用 查看範例
5-通話-MSGraph-管理身份 在 Azure 上使用受控身份的守護程式 查看範例

比較樣本模式與生產模式

Azure範例使用 TokenAcquirerFactory.GetDefaultInstance() 以簡化過程,這是推薦用於 簡單的控制台應用程式、原型及測試。 本指南展示了這兩種模式:

TokenAcquirerFactory 模式(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
  • 對於委派標記:使用CreateAuthorizationHeaderForUserAsyncWithAgentUserIdentity
  • 確保 API 權限與 token 類型相符(應用程式與委派)

令牌快取問題

問題: 代幣不會被緩存,這會迫使每次都重新獲得代幣。

Solution:

  • 針對代理使用者身份:在多個通話間重複使用同一 ClaimsPrincipal 實例
  • 驗證分散式快取連線(若使用 Redis/SQL)
  • 啟用偵錯記錄以查看快取操作

詳細診斷:日誌與診斷指南