本指南介绍如何在 .NET Framework、.NET Standard 2.0 和经典 .NET 应用程序(.NET 4.7.2+)中,将 Microsoft.Identity.Web 令牌缓存和证书包与 MSAL.NET 配合使用。
了解概述
从 Microsoft.Identity.Web 1.17+ 开始,您可以在非 ASP.NET Core 环境中将 MSAL.NET 与 Microsoft.Identity.Web 实用工具包一起使用。
确定包的优势
| 功能 | 益处 |
|---|---|
| 令牌缓存序列化 | 内存中、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 Framework)
- Worker Services (.NET Framework)
- .NET标准 2.0 库(跨平台兼容性)
- 非网页 MSAL.NET 应用程序
注释
有关 ASP.NET MVC/Web API 应用程序,请参阅 OWIN Integration。
选择软件包
选择与方案匹配的包。
标识 MSAL.NET 的核心包
| Package | Purpose | 依赖关系 | .NET目标 |
|---|---|---|---|
| Microsoft。Identity.Web.TokenCache | 令牌缓存序列化器,ClaimsPrincipal 扩展 |
最小 | .NET Standard 2.0 |
| Microsoft.Identity.Web.Certificate | 证书加载工具 | 最小 | .NET Standard 2.0 |
安装软件包
使用以下方法之一将包添加到项目。
程序包管理器 Console:
# 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 和 .NET Standard 环境。
配置令牌缓存序列化
了解令牌缓存适配器
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 DB 缓存:
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 语义
- 与 Azure Database for PostgreSQL 一起使用
- 需要 PostgreSQL 服务器
使用案例:已使用 PostgreSQL 作为主数据库的应用程序,或使用 Azure 托管的 PostgreSQL 数据库服务
生成完整的守护程序应用程序
以下示例演示使用客户端凭据和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 密钥保管库加载证书并创建机密客户端应用程序。
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 密钥保管库加载
通过指定保管库 URL 和证书名称来加载存储在Azure 密钥保管库中的证书。
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:
<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核心) | 从密钥保管库加载证书 |
遵循最佳做法
应用这些模式以生成可靠且安全的应用程序。
遵循建议的模式
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 密钥保管库或正确保护的证书存储中。
// 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) |
| 令牌缓存 | 需要手动序列化 | 通过扩展方法的内置适配器 |
| 证书 | 手动加载 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();
浏览相关内容
使用这些资源了解有关相关方案的详细信息。