Кэш токенов в Microsoft.Identity.Web

Кэширование маркеров повышает производительность приложений, надежность и взаимодействие с пользователем. Microsoft. Identity.Web предоставляет гибкие стратегии кэширования, которые балансирует производительность, сохраняемость и надежность эксплуатации.

Обзор

В этом разделе описывается, какие токены кэширует Microsoft.Identity.Web и почему кэширование важно для вашего приложения.

Какие маркеры кэшируются?

Microsoft. Identity.Web кэширует несколько типов токенов:

Тип маркера размера Объем Выселения
Маркеры доступа ~2 КБ За (user/app, tenant, resource) Автоматическое (на основе времени жизни)
Токены обновления Variable Учетная запись пользователя Вручную или на основе правил
Токены идентификатора ~2-7 КБ На пользователя Автоматический

Где применяется кэширование маркеров:

Почему кешировать токены?

Преимущества производительности:

  • Уменьшает количество обратных запросов к Microsoft Entra ID
  • Более быстрые вызовы API (L1: 10 мс и L2: <~30 мс и сеть: >100 мс)
  • Низкая задержка для конечных пользователей

Преимущества надежности:

  • Продолжает работать во время временных Microsoft Entra сбоев
  • Устойчивость к временным возмущениям в сети
  • Плавная деградация при сбое распределенного кэша

Преимущества затрат:

  • Уменьшает запросы проверки подлинности (избежание регулирования)
  • Более низкие Azure затраты на операции проверки подлинности

Быстрый старт

Быстро приступить к работе с одной из следующих конфигураций кэша в зависимости от среды.

Разработка: кэш в памяти

В следующем примере добавляется кэш токенов в памяти, подходящий для разработки и примеров:

using Microsoft.Identity.Web;

builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

Преимущества.

  • Простая настройка
  • Быстрая производительность
  • Нет внешних зависимостей

Недостатки:

  • Кэш теряется при перезапуске приложения. В веб-приложении пользователи остаются вошедыми через файл 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 Допустимо, если сеансы можно повторно установить
Рабочая среда с несколькими серверами Редис Общий кэш, высокая производительность, надежная
Приложения, размещенные в Azure Кэш Azure для Redis Нативная интеграция с Azure, управляемая услуга
Локальное предприятие SQL Server Использует существующую инфраструктуру
Среды PostgreSQL PostgreSQL Использует существующую базу данных PostgreSQL, знакомую семантику SQL
Высокобезопасные среды SQL Server + шифрование Размещение данных, шифрование данных в состоянии покоя
Тестирование распределенных сценариев Распределенная память Проверяет поведение кэша L2 без инфраструктуры

Реализации кэша

Microsoft. Identity.Web поддерживает несколько реализаций кэша. Выберите ту, которая соответствует требованиям к инфраструктуре и доступности.

Кэш в памяти

Когда следует использовать:

  • Разработка и тестирование
  • Развертывания с одним сервером с допустимым поведением перезапуска
  • Примеры и прототипы

Configuration:

Следующий код регистрирует кэш маркеров в памяти с параметрами по умолчанию:

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
    });

→ Дополнительные сведения о конфигурации кэша в памяти


Распределенный кэш (L2) с автоматической поддержкой L1

Когда следует использовать:

  • Развертывания на многосерверных промышленных средах
  • Приложения, требующие сохраняемости кэша при перезапуске
  • Сценарии с высоким уровнем доступности

Основная функция: Начиная с версии 1.8.0 Microsoft.Identity.Web, распределённый кэш автоматически включает кэш L1 в памяти для повышения производительности и надёжности.

Добавьте строку подключения Redis в appsettings.json:

{
  "ConnectionStrings": {
    "Redis": "localhost:6379"
  }
}

Затем зарегистрируйте распределенный кэш токенов и поставщик Redis в Program.cs:

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 для Redis

Чтобы использовать Кэш Azure для 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

Требуется пакет NuGet Microsoft.Extensions.Caching.Postgres.

appsettings.json:

{
  "ConnectionStrings": {
    "PostgresCache": "Host=localhost;Database=mydb;Username=myuser;Password=mypassword"
  },
  "PostgresCache": {
    "SchemaName": "public",
    "TableName": "token_cache",
    "CreateIfNotExists": true
  }
}

Затем зарегистрируйте кэш PostgreSQL в Program.cs:

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 — большие маркеры идентификатора с множеством утверждений вызывают проблемы
  • Конфликты области видимости Scope — не удается использовать с одноэлементным объектом TokenAcquisition (например, пакет SDK Microsoft Graph)
  • Требуется сопоставление сеансов . Не работает хорошо в сценариях балансировки нагрузки
  • Не рекомендуется . Вместо этого используйте распределенный кэш

Расширенная конфигурация

Эти параметры позволяют точно настроить поведение кэша для политик производительности, безопасности и вытеснения.

Элемент управления кэшем 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 часа скольжения для хорошего пользовательского интерфейса

→ Дополнительные сведения о стратегиях вытеснения кэша


Шифрование данных в состоянии покоя

Чтобы защитить данные конфиденциального токена в распределенных кэшах, включите шифрование с помощью ASP.NET Core Data Protection.

Одна машина

На одном компьютере включите шифрование со встроенным поставщиком защиты данных:

builder.Services.Configure<MsalDistributedTokenCacheAdapterOptions>(options =>
{
    options.Encrypt = true; // Uses ASP.NET Core Data Protection
});

Распределенные системы (несколько серверов)

Это важно

Распределенные системы по умолчанию не совместно используют ключи шифрования. Необходимо настроить общий доступ к ключам:

Azure Key Vault (рекомендуется):

Следующий код сохраняет ключи для Хранилище BLOB-объектов Azure и защищает их с помощью Azure Key Vault:

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 КБ Арендатор × ресурс Автоматическое вытеснение
Токены пользователей ~7 КБ Пользователь × Арендатор × Ресурс Необходимо вытеснение вручную
Маркеры обновления Variable User Долгоживущий

Планирование памяти

Для 500 одновременных пользователей , вызывающих 3 API:

  • Токены пользователя: 500 × 3 × 7 КБ = 10,5 МБ
  • С накладными расходами: ~15-20 МБ

Для 10 000 одновременных пользователей:

  • Маркеры пользователей: 10 000 × 3 × 7 КБ = 210 МБ
  • С учётом накладных расходов: ~300-350 МБ

Рекомендации: Задайте ограничение размера кэша L1 на основе ожидаемых одновременных пользователей.

Лучшие практики

Следуйте этим рекомендациям, чтобы обеспечить надежное и эффективное кэширование токенов.

Использование распределенного кэша в рабочей среде — Essential для развертываний с несколькими серверами

Установка соответствующих ограничений размера кэша — предотвращение роста несвязанной памяти

Настройка политик вытеснения — балансировка использования пользовательского интерфейса и памяти

Включение шифрования конфиденциальных данных — защита токенов в состоянии покоя

Мониторинг работоспособности кэша — отслеживание частоты попаданий, сбоев и производительности

Корректная обработка сбоев кэша L2. Кэш L1 обеспечивает устойчивость.

Поведение кэша — проверка сценариев перезапуска и переключения на резерв

Не используйте кэш распределенной памяти в рабочей среде — не сохраняемый или распределенный

Не используйте кэш сеансов . Имеет значительные ограничения

Не устанавливайте срок действия меньше времени существования маркера – приводит к ненужной повторной проверке подлинности.

Не забывайте о совместном использовании ключей шифрования . Распределенные системы нуждаются в общих ключах