Microsoft.Identity.Web 中的令牌缓存

令牌缓存可提高应用程序性能、可靠性和用户体验。 Microsoft。Identity.Web 提供灵活的缓存策略,用于平衡性能、持久性和操作可靠性。

概述

本部分介绍 Microsoft.Identity.Web 缓存的令牌以及为何缓存对您的应用程序很重要。

缓存了哪些令牌?

Microsoft。Identity.Web 缓存多种类型的令牌:

令牌类型 大小 Scope 驱逐
访问令牌 约 2 KB 每个(用户/应用、租户、资源) 自动(基于生命周期)
刷新令牌 Variable 每个用户帐户 手动或基于策略
ID 令牌 约 2-7 KB 每位用户 自动

令牌缓存适用位置:

为什么缓存令牌?

性能优势:

  • 减少往返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 连接字符串添加到 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 缓存确保复原能力

测试缓存行为 - 验证重启场景和故障切换

请勿在生产环境中使用分布式内存缓存 - 不持久或分布式

不要使用会话缓存 - 存在重大限制

不要将过期时间设置为短于令牌生存期 - 强制不必要的重新身份验证

不要忘记加密密钥共享 - 分布式系统需要共享密钥