將 MSAL.NET 整合進 Microsoft。.NET Framework 中的 Identity.Web

本指南教你如何將 Microsoft.Identity.Web 的 token 快取和憑證套件與 MSAL.NET 用於 .NET Framework、.NET Standard 2.0 和經典 .NET 應用程式(.NET 4.7.2+)。

了解整體概述

Microsoft.Identity.Web 1.17+開始,您可以在非 ASP.NET Core 環境中使用 Microsoft.Identity.Web 工具套件與 MSAL.NET。

識別套件的好處

Feature 優點
令牌快取序列化 可重複使用的快取適配器,適用於內存、SQL Server、Redis、Cosmos DB、PostgreSQL
憑證助手 從 KeyVault 中、檔案系統或憑證存儲庫簡化憑證載入
申請延伸 ClaimsPrincipal 操作的實用方法
.NET標準 2.0 相容於 .NET Framework 4.7.2+、.NET Core 及 .NET 5+
最小相依關係 不依賴 ASP.NET Core 的目標化套件

回顧支援情境

以下情境由目標工具套件支援。

  • .NET Framework 控制台應用程式(精靈場景)
  • Desktop Applications (.NET 框架)
  • Worker Services (.NET 框架)
  • .NET 標準 2.0 函式庫(跨平台相容性)
  • 非網頁 MSAL.NET 應用

備註

關於 ASP.NET MVC/Web API 應用,請參見 OWIN Integration


選擇套件

選擇符合你情況的方案。

識別 MSAL.NET 的核心套件

Package Purpose 依賴 .NET 目標
Microsoft。Identity.Web.TokenCache 標記快取序列化器與ClaimsPrincipal延伸模組 最小 .NET 標準 2.0
Microsoft.Identity.Web.Certificate 憑證載入工具 最小 .NET 標準 2.0

安裝套件

請使用以下其中一種方法將套件加入你的專案。

封裝管理員 控制台:

# Token cache serialization
Install-Package Microsoft.Identity.Web.TokenCache

# Certificate management
Install-Package Microsoft.Identity.Web.Certificate

.NET CLI:

dotnet add package Microsoft.Identity.Web.TokenCache
dotnet add package Microsoft.Identity.Web.Certificate

了解核心套件的限制

核心 Microsoft.Identity.Web 套件包含 ASP.NET Core相依關係(Microsoft.AspNetCore.*),其中包括:

  • 與 ASP.NET Framework 不相容
  • 不必要的擴充包大小
  • 製造相依衝突

改用針對性的套件 針對 .NET Framework 及 Standard .NET 情境。


配置令牌快取序列化

了解令牌快取轉接器

Microsoft。Identity.Web 提供與 MSAL.NET IConfidentialClientApplication 無縫相容的憑證快取適配器。

建立一個帶有標記快取的機密客戶端

以下範例建立一個機密客戶端應用程式,並附加記憶體中的令牌快取。

using Microsoft.Identity.Client;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.TokenCacheProviders;

public class MsalAppBuilder
{
    private static IConfidentialClientApplication _app;

    public static IConfidentialClientApplication BuildConfidentialClientApplication()
    {
        if (_app == null)
        {
            string clientId = ConfigurationManager.AppSettings["AzureAd:ClientId"];
            string clientSecret = ConfigurationManager.AppSettings["AzureAd:ClientSecret"];
            string tenantId = ConfigurationManager.AppSettings["AzureAd:TenantId"];

            // Create the confidential client application
            _app = ConfidentialClientApplicationBuilder.Create(clientId)
                .WithClientSecret(clientSecret)
                .WithTenantId(tenantId)
                .WithAuthority(AzureCloudInstance.AzurePublic, tenantId)
                .Build();

            // Add token cache serialization (choose one option below)
            _app.AddInMemoryTokenCache();
        }

        return _app;
    }
}

選擇代幣快取選項

選擇最適合你部署情境的快取提供者。

設定記憶體內標記快取

以下範例新增了一個簡單的記憶體內快取:

using Microsoft.Identity.Web.TokenCacheProviders;

_app.AddInMemoryTokenCache();

記憶體內快取,具有大小限制 (Microsoft。Identity.Web 1.20+):

using Microsoft.Extensions.Caching.Memory;

_app.AddInMemoryTokenCache(services =>
{
    // Configure memory cache options
    services.Configure<MemoryCacheOptions>(options =>
    {
        options.SizeLimit = 5000000;  // 5 MB limit
    });
});

特性

  • 快速存取
  • 沒有外部相依性
  • 不在各流程間共享
  • 應用程式重啟時遺失

使用情境: 單實例主控台應用程式、桌面應用程式


配置分散式內存令牌快取

請使用以下程式碼為多實例環境新增分散式記憶體快取:

_app.AddDistributedTokenCaches(services =>
{
    // Requires: Microsoft.Extensions.Caching.Memory (NuGet)
    services.AddDistributedMemoryCache();
});

特性

  • 共享於應用程式實例間
  • 更適合負載平衡的場景
  • 需要額外的 NuGet 套件
  • 重新啟動應用程式時還是找不到

使用情境: 多重實例服務,允許令牌的重新取得


配置 SQL Server 令牌緩存

請使用以下程式碼來新增持久且分散式的 SQL Server 快取:

using Microsoft.Extensions.Caching.SqlServer;

_app.AddDistributedTokenCaches(services =>
{
    // Requires: Microsoft.Extensions.Caching.SqlServer (NuGet)
    services.AddDistributedSqlServerCache(options =>
    {
        options.ConnectionString = ConfigurationManager.ConnectionStrings["TokenCache"].ConnectionString;
        options.SchemaName = "dbo";
        options.TableName = "TokenCache";

        // IMPORTANT: Set expiration above token lifetime
        // Access tokens typically expire after 1 hour
        options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);
    });
});

執行以下 SQL 來建立所需的快取資料表:

-- Create the cache table
CREATE TABLE [dbo].[TokenCache] (
    [Id] NVARCHAR(449) NOT NULL,
    [Value] VARBINARY(MAX) NOT NULL,
    [ExpiresAtTime] DATETIMEOFFSET NOT NULL,
    [SlidingExpirationInSeconds] BIGINT NULL,
    [AbsoluteExpiration] DATETIMEOFFSET NULL,
    PRIMARY KEY ([Id])
);

-- Create index for performance
CREATE INDEX [Index_ExpiresAtTime] ON [dbo].[TokenCache] ([ExpiresAtTime]);

特性

  • 重啟後的持續性
  • 跨多個實例共享
  • 可靠且可擴展
  • 需要安裝 SQL Server

使用情境: 生產守護程序服務、排程任務、多實例工作者


配置 Redis 記號快取

請使用以下程式碼新增高效能 Redis 分散式快取:

using StackExchange.Redis;
using Microsoft.Extensions.Caching.StackExchangeRedis;

_app.AddDistributedTokenCaches(services =>
{
    // Requires: Microsoft.Extensions.Caching.StackExchangeRedis (NuGet)
    services.AddStackExchangeRedisCache(options =>
    {
        options.Configuration = ConfigurationManager.AppSettings["Redis:ConnectionString"];
        options.InstanceName = "TokenCache_";
    });
});

以下範例展示了一個生產環境可用的 Redis 配置:

services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = ConfigurationManager.AppSettings["Redis:ConnectionString"];
    options.InstanceName = "MyDaemonApp_";

    // Optional: Configure Redis options
    options.ConfigurationOptions = new ConfigurationOptions
    {
        AbortOnConnectFail = false,
        ConnectTimeout = 5000,
        SyncTimeout = 5000
    };
});

特性

  • 非常快
  • 跨實例共享
  • 持續性(啟用 Redis 持久化)
  • 需要 Redis 伺服器

使用情境: 高容量守護程序應用程式、分散式系統、微服務


配置 Cosmos DB 的令牌快取系統

請使用以下程式碼新增全球分布式的 Cosmos 資料庫快取:

using Microsoft.Extensions.Caching.Cosmos;

_app.AddDistributedTokenCaches(services =>
{
    // Requires: Microsoft.Extensions.Caching.Cosmos (preview)
    services.AddCosmosCache(options =>
    {
        options.ContainerName = "TokenCache";
        options.DatabaseName = "IdentityCache";
        options.ClientBuilder = new CosmosClientBuilder(
            ConfigurationManager.AppSettings["CosmosConnectionString"]);
        options.CreateIfNotExists = true;
    });
});

特性

  • 全球分布
  • 高可用性
  • 自動縮放
  • 延遲比 Redis 高
  • 較高成本

使用情境: 全球精靈服務、地理分布式應用


配置 PostgreSQL 令牌快取

請使用以下程式碼新增分散式 PostgreSQL 快取:

_app.AddDistributedTokenCaches(services =>
{
    // Requires: Microsoft.Extensions.Caching.Postgres (NuGet)
    services.AddDistributedPostgresCache(options =>
    {
        options.ConnectionString = ConfigurationManager.ConnectionStrings["PostgresCache"].ConnectionString;
        options.SchemaName = ConfigurationManager.AppSettings["PostgresCache:SchemaName"];
        options.TableName = ConfigurationManager.AppSettings["PostgresCache:TableName"];
        options.CreateIfNotExists = bool.Parse(
            ConfigurationManager.AppSettings["PostgresCache:CreateIfNotExists"] ?? "true");

        // Set expiration above token lifetime.
        // Access tokens typically expire after 1 hour.
        options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);
    });
});

特性

  • 重啟後的持續性
  • 跨多個實例共享
  • 熟悉的 SQL 語意
  • 能與 適用於 PostgreSQL 的 Azure 資料庫 配合使用
  • 需要 PostgreSQL 伺服器

使用案例: 已使用 PostgreSQL 作為主要資料庫的應用程式,或使用 Azure 託管服務適用於 PostgreSQL 的 Azure 資料庫


建立完整的守護程序應用程式

以下範例展示了一個完整的守護進程應用程式,利用用戶端憑證與 SQL Server 憑證快取取得權杖。

using Microsoft.Identity.Client;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.TokenCacheProviders;
using System;
using System.Threading.Tasks;

namespace DaemonApp
{
    class Program
    {
        private static IConfidentialClientApplication _app;

        static async Task Main(string[] args)
        {
            // Build confidential client with token cache
            _app = BuildConfidentialClient();

            // Acquire token for app-only access
            string[] scopes = new[] { "https://graph.microsoft.com/.default" };

            try
            {
                var result = await _app.AcquireTokenForClient(scopes)
                    .ExecuteAsync();

                Console.WriteLine($"Token acquired successfully!");
                Console.WriteLine($"Token source: {result.AuthenticationResultMetadata.TokenSource}");
                Console.WriteLine($"Expires on: {result.ExpiresOn}");

                // Use token to call API
                await CallProtectedApi(result.AccessToken);
            }
            catch (MsalServiceException ex)
            {
                Console.WriteLine($"Error acquiring token: {ex.ErrorCode}");
                Console.WriteLine($"CorrelationId: {ex.CorrelationId}");
            }
        }

        private static IConfidentialClientApplication BuildConfidentialClient()
        {
            var app = ConfidentialClientApplicationBuilder
                .Create(ConfigurationManager.AppSettings["ClientId"])
                .WithClientSecret(ConfigurationManager.AppSettings["ClientSecret"])
                .WithTenantId(ConfigurationManager.AppSettings["TenantId"])
                .Build();

            // Add SQL Server token cache for persistence
            app.AddDistributedTokenCaches(services =>
            {
                services.AddDistributedSqlServerCache(options =>
                {
                    options.ConnectionString = ConfigurationManager
                        .ConnectionStrings["TokenCache"].ConnectionString;
                    options.SchemaName = "dbo";
                    options.TableName = "TokenCache";
                    options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);
                });
            });

            return app;
        }

        private static async Task CallProtectedApi(string accessToken)
        {
            // Your API call logic
        }
    }
}

管理憑證

了解憑證載入

Microsoft。Identity.Web 簡化了來自各種來源的憑證載入,以支援用戶端憑證流程。

使用 DefaultCertificateLoader 載入憑證

以下範例示範如何從 Azure Key Vault 載入憑證並建立機密客戶端應用程式。

using Microsoft.Identity.Web;
using Microsoft.Identity.Client;

public class CertificateHelper
{
    public static IConfidentialClientApplication CreateAppWithCertificate()
    {
        string clientId = ConfigurationManager.AppSettings["AzureAd:ClientId"];
        string tenantId = ConfigurationManager.AppSettings["AzureAd:TenantId"];

        // Define certificate source
        var certDescription = CertificateDescription.FromKeyVault(
            keyVaultUrl: "https://my-keyvault.vault.azure.net",
            keyVaultCertificateName: "MyCertificate"
        );

        // Load certificate
        ICertificateLoader certificateLoader = new DefaultCertificateLoader();
        certificateLoader.LoadIfNeeded(certDescription);

        // Create confidential client with certificate
        var app = ConfidentialClientApplicationBuilder.Create(clientId)
            .WithCertificate(certDescription.Certificate)
            .WithTenantId(tenantId)
            .Build();

        // Add token cache
        app.AddInMemoryTokenCache();

        return app;
    }
}

選擇憑證來源

從 Azure Key Vault 加載

透過指定 Vault URL 和憑證名稱,載入儲存在 Azure Key Vault 中的憑證。

var certDescription = CertificateDescription.FromKeyVault(
    keyVaultUrl: "https://my-keyvault.vault.azure.net",
    keyVaultCertificateName: "MyApplicationCert"
);

ICertificateLoader loader = new DefaultCertificateLoader();
loader.LoadIfNeeded(certDescription);

var app = ConfidentialClientApplicationBuilder.Create(clientId)
    .WithCertificate(certDescription.Certificate)
    .WithTenantId(tenantId)
    .Build();

先決條件

  • 管理身份或服務主體具備 金鑰保存庫 存取權
  • Azure.Identity NuGet 套件
  • 金鑰保存庫權限:憑證上的 Get

從憑證儲存庫載入

從 Windows 憑證儲存庫以區別名稱載入憑證。

var certDescription = CertificateDescription.FromStoreWithDistinguishedName(
    distinguishedName: "CN=MyApp.contoso.com",
    storeName: StoreName.My,
    storeLocation: StoreLocation.CurrentUser
);

ICertificateLoader loader = new DefaultCertificateLoader();
loader.LoadIfNeeded(certDescription);

var app = ConfidentialClientApplicationBuilder.Create(clientId)
    .WithCertificate(certDescription.Certificate)
    .WithTenantId(tenantId)
    .Build();

您也可以透過指紋找到證明:

var certDescription = CertificateDescription.FromStoreWithThumbprint(
    thumbprint: "ABCDEF1234567890ABCDEF1234567890ABCDEF12",
    storeName: StoreName.My,
    storeLocation: StoreLocation.LocalMachine
);

從檔案系統載入

從本地檔案系統的 PFX 檔案載入憑證。

var certDescription = CertificateDescription.FromPath(
    path: @"C:\Certificates\MyAppCert.pfx",
    password: ConfigurationManager.AppSettings["Certificate:Password"]
);

ICertificateLoader loader = new DefaultCertificateLoader();
loader.LoadIfNeeded(certDescription);

var app = ConfidentialClientApplicationBuilder.Create(clientId)
    .WithCertificate(certDescription.Certificate)
    .WithTenantId(tenantId)
    .Build();

安全提示: 千萬不要硬編碼密碼。 使用安全設定。


從 Base64 編碼的字串載入

從儲存在設定中的 Base64 編碼字串載入憑證。

string base64Cert = ConfigurationManager.AppSettings["Certificate:Base64"];

var certDescription = CertificateDescription.FromBase64Encoded(
    base64EncodedValue: base64Cert,
    password: ConfigurationManager.AppSettings["Certificate:Password"]  // Optional
);

ICertificateLoader loader = new DefaultCertificateLoader();
loader.LoadIfNeeded(certDescription);

從 App.config 設定憑證載入

在你的 App.config 檔案中定義憑證設定,並在執行時載入。

App.config:

<appSettings>
  <add key="AzureAd:ClientId" value="your-client-id" />
  <add key="AzureAd:TenantId" value="your-tenant-id" />

  <!-- Option 1: KeyVault -->
  <add key="Certificate:SourceType" value="KeyVault" />
  <add key="Certificate:KeyVaultUrl" value="https://my-vault.vault.azure.net" />
  <add key="Certificate:KeyVaultCertificateName" value="MyCert" />

  <!-- Option 2: Store -->
  <!--
  <add key="Certificate:SourceType" value="StoreWithThumbprint" />
  <add key="Certificate:CertificateThumbprint" value="ABCD..." />
  <add key="Certificate:CertificateStorePath" value="CurrentUser/My" />
  -->
</appSettings>

<connectionStrings>
  <add name="TokenCache"
       connectionString="Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=TokenCache;Integrated Security=True;" />
</connectionStrings>

請使用以下輔助工具方法根據設定載入憑證:

public static CertificateDescription GetCertificateFromConfig()
{
    string sourceType = ConfigurationManager.AppSettings["Certificate:SourceType"];

    return sourceType switch
    {
        "KeyVault" => CertificateDescription.FromKeyVault(
            ConfigurationManager.AppSettings["Certificate:KeyVaultUrl"],
            ConfigurationManager.AppSettings["Certificate:KeyVaultCertificateName"]
        ),

        "StoreWithThumbprint" => CertificateDescription.FromStoreWithThumbprint(
            ConfigurationManager.AppSettings["Certificate:CertificateThumbprint"],
            StoreName.My,
            StoreLocation.CurrentUser
        ),

        _ => throw new ConfigurationErrorsException("Invalid certificate source type")
    };
}

探索範例應用

請檢視這些範例,看看實際運作的實作。

檢視官方 Microsoft 範例

下表列出官方範例,展示代幣快取與憑證載入。

範例 平台 說明
ConfidentialClientTokenCache 控制台(.NET 框架) 標記快取序列化模式
active-directory-dotnetcore-daemon-v2 主機(.NET Core) 從 金鑰保存庫 載入憑證

遵循最佳做法

應用這些模式來建立可靠且安全的應用程式。

1. 使用 IConfidentialClientApplication 的單例模式:

建立一個單一實例,並在整個應用程式中重複使用。

private static IConfidentialClientApplication _app;

public static IConfidentialClientApplication GetApp()
{
    if (_app == null)
    {
        _app = ConfidentialClientApplicationBuilder.Create(clientId)
            .WithClientSecret(clientSecret)
            .WithTenantId(tenantId)
            .Build();

        _app.AddDistributedTokenCaches(/* ... */);
    }

    return _app;
}

2. 設定適當的令牌快取到期日:

設定超過代幣壽命的滑動到期,以防止不必要的重新取得。

// Access tokens typically expire after 1 hour
// Set cache expiration ABOVE token lifetime
options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);

3. 使用安全的憑證儲存:

將憑證存放在 Azure Key Vault 或適當安全的憑證儲存庫中。

// Azure Key Vault (production)
var cert = CertificateDescription.FromKeyVault(keyVaultUrl, certName);

// Certificate store with proper permissions
var cert = CertificateDescription.FromStoreWithThumbprint(
    thumbprint, StoreName.My, StoreLocation.LocalMachine);

4. 實施正確的錯誤處理:

偵測 MSAL 例外,並記錄相關 ID 以便排除故障。

try
{
    var result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
}
catch (MsalServiceException ex)
{
    logger.Error($"Token acquisition failed. CorrelationId: {ex.CorrelationId}, ErrorCode: {ex.ErrorCode}");
    throw;
}

5. 使用分散式快取進行生產:

分散式快取會在不同實例間共享標記,並在重啟後持續存在。

// Correct for daemon services
app.AddDistributedTokenCaches(services =>
{
    services.AddDistributedSqlServerCache(/* ... */);
});

避免常見錯誤

1. 不要重複建立新的 IConfidentialClientApplication 實例:

// Wrong - creates new instance every time
public void AcquireToken()
{
    var app = ConfidentialClientApplicationBuilder.Create(clientId).Build();
    // ...
}

// Correct - use singleton
private static readonly IConfidentialClientApplication _app = BuildApp();

2. 不要硬編碼秘密:

// Wrong
.WithClientSecret("supersecretvalue123")

// Correct
.WithClientSecret(ConfigurationManager.AppSettings["AzureAd:ClientSecret"])

3. 不要在多實例服務中使用記憶體快取:

// Wrong for services with multiple instances
app.AddInMemoryTokenCache();

// Correct - use distributed cache
app.AddDistributedTokenCaches(services =>
{
    services.AddDistributedSqlServerCache(/* ... */);
});

4. 不要忽略憑證驗證:

// Wrong - skips validation
ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, errors) => true;

// Correct - validate certificates properly

從 ADAL.NET 遷移

檢視主要差異,並更新你的程式碼,使其能使用 MSAL.NET 和 Microsoft.Identity.Web。

了解關鍵差異

層面 ADAL.NET(已棄用) MSAL.NET + Microsoft。Identity.Web
範圍 資源型(https://graph.microsoft.com 基於範圍的(https://graph.microsoft.com/.default
代幣快取 需手動序列化 透過擴充方法內建的轉接器
Certificates 手動 X509Certificate2 裝載 DefaultCertificateLoader 擁有多重來源
授權單位 建造時固定 可依請求覆寫

比較遷移範例

ADAL.NET(舊):

AuthenticationContext authContext = new AuthenticationContext(authority);
ClientCredential credential = new ClientCredential(clientId, clientSecret);
AuthenticationResult result = await authContext.AcquireTokenAsync(resource, credential);

MSAL.NET 與 Microsoft。Identity.Web(新):

var app = ConfidentialClientApplicationBuilder.Create(clientId)
    .WithClientSecret(clientSecret)
    .WithTenantId(tenantId)
    .Build();

app.AddInMemoryTokenCache();  // Add token cache

string[] scopes = new[] { "https://graph.microsoft.com/.default" };
AuthenticationResult result = await app.AcquireTokenForClient(scopes).ExecuteAsync();

利用這些資源了解更多相關情境。