本指南教你如何將 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.IdentityNuGet 套件 - 金鑰保存庫權限:憑證上的
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();
探索相關內容
利用這些資源了解更多相關情境。