令牌缓存可提高应用程序性能、可靠性和用户体验。 Microsoft。Identity.Web 提供灵活的缓存策略,用于平衡性能、持久性和操作可靠性。
概述
本部分介绍 Microsoft.Identity.Web 缓存的令牌以及为何缓存对您的应用程序很重要。
缓存了哪些令牌?
Microsoft。Identity.Web 缓存多种类型的令牌:
| 令牌类型 | 大小 | Scope | 驱逐 |
|---|---|---|---|
| 访问令牌 | 约 2 KB | 每个(用户/应用、租户、资源) | 自动(基于生命周期) |
| 刷新令牌 | Variable | 每个用户帐户 | 手动或基于策略 |
| ID 令牌 | 约 2-7 KB | 每位用户 | 自动 |
令牌缓存适用位置:
- 调用 API 的 Web 应用 - 用于委派访问的用户令牌
- 调用下游 API 的 Web API - OBO 令牌(需要仔细逐出策略)
- 守护程序应用程序 - 服务到服务调用的仅限应用的令牌
为什么缓存令牌?
性能优势:
- 减少往返Microsoft Entra ID
- 更快的 API 调用(L1:<10ms vs L2:~30ms vs 网络:>100ms)
- 最终用户的延迟较低
可靠性优势:
- 在临时Microsoft Entra中断期间继续工作
- 具备抗扰性的网络瞬变
- 分布式缓存失败时平稳降级
成本优势:
- 减少身份验证请求(限制避免)
- 降低Azure身份验证操作的成本
快速入门
根据环境,使用以下缓存配置之一快速开始。
开发 - 内存缓存
以下示例添加了适用于开发和示例的内存中令牌缓存:
using Microsoft.Identity.Web;
builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
优点:
- 安装简单
- 快速性能
- 无外部依赖项
缺点:
- 应用重启时缓存丢失。 在 Web 应用中,用户仍通过 Cookie 登录,但必须重新登录才能获取访问令牌并重新填充缓存
- 不适合生产多服务器部署
- 不跨应用程序实例共享
生产 - 分布式缓存
对于生产应用程序,尤其是多服务器部署,请使用由 Redis 或其他提供程序支持的分布式缓存:
using Microsoft.Identity.Web;
builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
.EnableTokenAcquisitionToCallDownstreamApi()
.AddDistributedTokenCaches();
// Choose your cache implementation
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration.GetConnectionString("Redis");
options.InstanceName = "MyApp_";
});
优点:
- 应用重启后继续运行
- 在所有应用程序实例之间共享
- 自动 L1+L2 缓存
缺点:
- 需要外部缓存基础结构
- 其他配置复杂性
- 缓存操作的网络延迟
选择缓存策略
使用以下决策流程图和矩阵选择最适合部署的缓存策略。
flowchart TD
Start([Token Caching<br/>Decision]) --> Q1{Production<br/>Environment?}
Q1 -->|No - Dev/Test| DevChoice[In-Memory Cache<br/>AddInMemoryTokenCaches]
Q1 -->|Yes| Q2{Multiple Server<br/>Instances?}
Q2 -->|No - Single Server| Q3{App Restarts<br/>Acceptable?}
Q3 -->|Yes| DevChoice
Q3 -->|No| DistChoice
Q2 -->|Yes| DistChoice[Distributed Cache<br/>AddDistributedTokenCaches]
DistChoice --> Q4{Cache<br/>Implementation?}
Q4 -->|High Performance| Redis[Redis Cache<br/>StackExchange.Redis<br/>⭐ Recommended]
Q4 -->|Azure Native| Azure[Azure Cache for Redis,<br/>Azure Cosmos DB,<br/>or Azure Database for PostgreSQL]
Q4 -->|On-Premises| SQL[SQL Server Cache<br/>AddDistributedSqlServerCache]
Q4 -->|Testing| DistMem[Distributed Memory<br/>Not for production]
Redis --> L1L2[Automatic L1+L2<br/>Caching]
Azure --> L1L2
SQL --> L1L2
DistMem --> L1L2
L1L2 --> Config[Configure Options<br/>MsalDistributedTokenCacheAdapterOptions]
DevChoice --> MemConfig[Configure Memory Options<br/>MsalMemoryTokenCacheOptions]
style Start fill:#e1f5ff
style DevChoice fill:#d4edda
style DistChoice fill:#fff3cd
style Redis fill:#d1ecf1
style L1L2 fill:#f8d7da
决策矩阵
下表汇总了常见部署方案的建议缓存类型。
| 情景 | 建议的缓存 | 理由 |
|---|---|---|
| 本地开发 | In-Memory | 简单性,无需基础结构 |
| 示例/演示 | In-Memory | 轻松设置演示 |
| 单服务器生产(重启正常) | In-Memory | 如果可以重新建立会话,可接受 |
| 多服务器生产 | Redis | 共享缓存,高性能,可靠 |
| Azure托管的应用程序 | Azure Cache for Redis | 原生 Azure 集成,托管服务 |
| 本地企业 | SQL Server | 利用现有基础结构 |
| PostgreSQL 环境 | PostgreSQL | 使用现有的 PostgreSQL 数据库,熟悉的 SQL 语义 |
| 高安全性环境 | SQL Server + 加密 | 数据驻留、静态加密 |
| 测试分布式方案 | 分布式内存 | 测试不带基础结构的 L2 缓存行为 |
缓存实现
Microsoft。Identity.Web 支持多个缓存实现。 选择与基础结构和可用性要求匹配的一个。
内存中缓存
何时使用:
- 开发和测试
- 具有可接受的重启行为的单服务器部署
- 示例和原型
配置:
以下代码使用默认设置注册内存中令牌缓存:
builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
使用自定义选项:
您可以通过传递选项来设置过期和大小限制:
builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches(options =>
{
// Token cache entry will expire after this duration
options.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);
// Limit cache size (default is unlimited)
options.SizeLimit = 500 * 1024 * 1024; // 500 MB
});
具有自动 L1 支持的分布式缓存 (L2)
何时使用:
- 生产环境多服务器部署
- 需要在重启后保持缓存持久性的应用程序
- 高可用性场景
关键功能:从 Microsoft.Identity.Web v1.8.0 开始,分布式缓存会自动包含内存 L1 缓存,以提高性能和可靠性。
Redis 缓存(建议)
将 Redis 连接字符串添加到 appsettings.json:
{
"ConnectionStrings": {
"Redis": "localhost:6379"
}
}
然后在 Program.cs中注册分布式令牌缓存和 Redis 提供程序:
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.TokenCacheProviders.Distributed;
builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
.EnableTokenAcquisitionToCallDownstreamApi()
.AddDistributedTokenCaches();
// Redis cache implementation
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration.GetConnectionString("Redis");
options.InstanceName = "MyApp_"; // Unique prefix per application
});
// Optional: Configure distributed cache behavior
builder.Services.Configure<MsalDistributedTokenCacheAdapterOptions>(options =>
{
// Control L1 cache size
options.L1CacheOptions.SizeLimit = 500 * 1024 * 1024; // 500 MB
// Handle L2 cache failures gracefully
options.OnL2CacheFailure = (exception) =>
{
if (exception is StackExchange.Redis.RedisConnectionException)
{
// Log the failure
// Optionally attempt reconnection
return true; // Retry the operation
}
return false; // Don't retry
};
});
Azure Cache for Redis
若要使用Azure Cache for Redis,请将缓存注册到Azure 连接字符串:
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration.GetConnectionString("AzureRedis");
options.InstanceName = "MyApp_";
});
连接字符串格式:
<cache-name>.redis.cache.windows.net:6380,password=<access-key>,ssl=True,abortConnect=False
SQL Server缓存
以下示例将SQL Server配置为分布式缓存后端:
builder.Services.AddDistributedSqlServerCache(options =>
{
options.ConnectionString = builder.Configuration.GetConnectionString("TokenCacheDb");
options.SchemaName = "dbo";
options.TableName = "TokenCache";
// Set expiration longer than access token lifetime (default 1 hour)
// This prevents cache entries from expiring before tokens
options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);
});
Azure Cosmos DB缓存
以下示例将Azure Cosmos DB配置为分布式缓存后端:
builder.Services.AddCosmosCache((CosmosCacheOptions options) =>
{
options.ContainerName = builder.Configuration["CosmosCache:ContainerName"];
options.DatabaseName = builder.Configuration["CosmosCache:DatabaseName"];
options.ClientBuilder = new CosmosClientBuilder(
builder.Configuration["CosmosCache:ConnectionString"]);
options.CreateIfNotExists = true;
});
PostgreSQL 缓存
需要 Microsoft.Extensions.Caching.Postgres NuGet 包。
appsettings.json:
{
"ConnectionStrings": {
"PostgresCache": "Host=localhost;Database=mydb;Username=myuser;Password=mypassword"
},
"PostgresCache": {
"SchemaName": "public",
"TableName": "token_cache",
"CreateIfNotExists": true
}
}
然后在 Program.cs中注册 PostgreSQL 缓存:
builder.Services.AddDistributedPostgresCache(options =>
{
options.ConnectionString = builder.Configuration.GetConnectionString("PostgresCache");
options.SchemaName = builder.Configuration["PostgresCache:SchemaName"];
options.TableName = builder.Configuration["PostgresCache:TableName"];
options.CreateIfNotExists = builder.Configuration.GetValue<bool>("PostgresCache:CreateIfNotExists");
options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);
});
会话缓存(不建议)
注意
基于会话的缓存具有重大限制。 请改用分布式缓存。
以下示例演示了基于会话的令牌缓存以供参考:
using Microsoft.Identity.Web.TokenCacheProviders.Session;
// In Program.cs
builder.Services.AddSession();
builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
.EnableTokenAcquisitionToCallDownstreamApi()
.AddSessionTokenCaches();
// In middleware pipeline
app.UseSession(); // Must be before UseAuthentication()
app.UseAuthentication();
app.UseAuthorization();
Limitations:
- Cookie 大小问题 - 含有大量声明的大型 ID 令牌会引发问题
-
Scope 冲突 - 不能与单例
TokenAcquisition一起使用(例如,Microsoft Graph SDK) - 需要会话相关性 - 在负载均衡方案中效果不佳
- 不建议 - 改用分布式缓存
高级配置
通过这些选项,可以微调性能、安全性和逐出策略的缓存行为。
L1 缓存控件
使用分布式缓存时,L1(内存中)缓存可提高性能。 以下代码配置 L1 缓存大小和行为:
builder.Services.Configure<MsalDistributedTokenCacheAdapterOptions>(options =>
{
// Control L1 cache size (default: 500 MB)
options.L1CacheOptions.SizeLimit = 100 * 1024 * 1024; // 100 MB
// Disable L1 cache if session affinity is not available
// (forces all requests to use L2 cache for consistency)
options.DisableL1Cache = false;
});
何时禁用 L1:
- 负载均衡器中没有会话相关性
- 由于缓存不一致,用户经常被提示执行 MFA验证
- 权衡:L2 访问速度较慢(约 30 毫秒与约 10 毫秒)
缓存逐出策略
逐出策略控制何时删除缓存令牌。 以下代码设置绝对过期和滑动过期:
builder.Services.Configure<MsalDistributedTokenCacheAdapterOptions>(options =>
{
// Absolute expiration (removed after this time, regardless of use)
options.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(72);
// Sliding expiration (renewed on each access)
options.SlidingExpiration = TimeSpan.FromHours(2);
});
还可以通过 appsettings.json配置逐出:
{
"TokenCacheOptions": {
"AbsoluteExpirationRelativeToNow": "72:00:00",
"SlidingExpiration": "02:00:00"
}
}
builder.Services.Configure<MsalDistributedTokenCacheAdapterOptions>(
builder.Configuration.GetSection("TokenCacheOptions"));
建议:
- 设置过期时间长于 令牌的生命周期,(令牌通常在 1 小时内过期)
- 默认值:90 分钟滑动过期
- 内存使用情况与用户体验之间的平衡
- 请考虑:72 小时绝对 + 2 小时动态,适合良好的用户体验 (UX)
静态加密
若要保护分布式缓存中的敏感令牌数据,请通过 ASP.NET Core数据保护启用加密。
单台计算机
在单个计算机上,使用内置数据保护提供程序启用加密:
builder.Services.Configure<MsalDistributedTokenCacheAdapterOptions>(options =>
{
options.Encrypt = true; // Uses ASP.NET Core Data Protection
});
分布式系统(多台服务器)
重要
默认情况下,分布式系统 不会 共享加密密钥。 必须配置密钥共享:
Azure 密钥保管库(建议):
以下代码保存密钥以Azure Blob 存储,并使用Azure 密钥保管库对其进行保护:
using Microsoft.AspNetCore.DataProtection;
builder.Services.AddDataProtection()
.PersistKeysToAzureBlobStorage(new Uri(builder.Configuration["DataProtection:BlobUri"]))
.ProtectKeysWithAzureKeyVault(
new Uri(builder.Configuration["DataProtection:KeyIdentifier"]),
new DefaultAzureCredential());
基于证书:
以下代码将密钥保存到文件共享,并使用 X.509 证书对其进行保护:
builder.Services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\keys"))
.ProtectKeysWithCertificate(
new X509Certificate2("current.pfx", builder.Configuration["CertPassword"]))
.UnprotectKeysWithAnyCertificate(
new X509Certificate2("current.pfx", builder.Configuration["CertPassword"]),
new X509Certificate2("previous.pfx", builder.Configuration["PrevCertPassword"]));
缓存性能注意事项
使用以下估计计划应用程序的缓存容量。
令牌大小估计
| 令牌类型 | 典型大小 | 每 | 备注 |
|---|---|---|---|
| 应用令牌 | 约 2 KB | 租户×资源 | 自动逐出 |
| 用户令牌 | 约 7 KB | 用户×租户×资源 | 需要手动逐出 |
| 刷新令牌 | Variable | 用户 | 长寿命 |
内存规划
对于 500 个并发用户 ,调用 3 个 API:
- 用户令牌:500 × 3 × 7 KB = 10.5 MB
- 开销 约为 15-20 MB
对于 10,000 个并发用户:
- 用户令牌:10,000 × 3 × 7 KB = 210 MB
- 包括开销在内:约为 300-350 MB
建议: 根据预期的并发用户设置 L1 缓存大小限制。
最佳做法
遵循以下准则,确保可靠且高效的令牌缓存。
在生产环境中使用分布式缓存 - 多服务器部署至关重要
设置适当的缓存大小限制 - 防止未绑定内存增长
配置逐出策略 - 平衡 UX 和内存使用情况
为敏感数据启用加密 - 保护静态令牌
监视缓存运行状况 - 跟踪命中率、故障和性能
正常处理 L2 缓存故障 - L1 缓存确保复原能力
测试缓存行为 - 验证重启场景和故障切换
请勿在生产环境中使用分布式内存缓存 - 不持久或分布式
不要使用会话缓存 - 存在重大限制
不要将过期时间设置为短于令牌生存期 - 强制不必要的重新身份验证
不要忘记加密密钥共享 - 分布式系统需要共享密钥