Condividi tramite


Cache dei token in Microsoft.Identity.Web

La memorizzazione nella cache dei token è fondamentale per le prestazioni, l'affidabilità e l'esperienza utente dell'applicazione. Microsoft. Identity.Web offre strategie flessibili di memorizzazione nella cache dei token che bilanciano le prestazioni, la persistenza e l'affidabilità operativa.

Informazioni generali

Che cosa sono i token memorizzati nella cache?

Microsoft. Identity.Web memorizza nella cache diversi tipi di token:

Tipo di token Dimensione Ambito Sfratto
Token di accesso ~2 KB Per (utente/app, tenant, risorsa) Automatico (basato sulla durata)
Token di aggiornamento Variable Per account utente Manuale o basato su criteri
Token ID ~2-7 KB Per utente Automatico

Dove si applica la memorizzazione nella cache dei token:

Perché memorizzare nella cache i token?

Vantaggi delle prestazioni:

  • Riduce i round trip per Microsoft Entra ID
  • Chiamate API più veloci (L1: <10ms vs L2: ~30ms vs rete: >100ms)
  • Bassa latenza per gli utenti finali

Vantaggi dell'affidabilità:

  • Continua a funzionare durante le interruzioni temporanee di Entra ID
  • Resiliente ai temporanei di rete
  • Degradazione graduale quando la cache distribuita fallisce

Vantaggi dei costi:

  • Riduce le richieste di autenticazione (limitazione della frequenza)
  • Costi di Azure inferiori per le operazioni di autenticazione

Avvio rapido

Sviluppo - cache in memoria

Per lo sviluppo e gli esempi, usare la cache in memoria:

using Microsoft.Identity.Web;

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

Vantaggi:

  • Impostazione semplice
  • Prestazioni veloci
  • Nessuna dipendenza esterna

Svantaggi:

  • Cache persa al riavvio dell'app. In una app web sarai collegato (con il cookie), ma sarà necessario ricollegarti per ottenere un token di accesso e popolare la cache dei token.
  • Non adatto per le distribuzioni multiserver di produzione
  • Non condiviso tra istanze dell'applicazione

Produzione - Cache distribuita

Per le applicazioni di produzione, in particolare le distribuzioni multiserver:

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

Vantaggi:

  • Sopravvive ai riavvii dell'app
  • Condiviso tra tutte le istanze dell'applicazione
  • Memorizzazione automatica nella cache L1+L2

Svantaggi:

  • Richiede l'infrastruttura della cache esterna
  • Complessità aggiuntiva della configurazione
  • Latenza di rete per le operazioni della cache

Scelta di una strategia di cache

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

Matrice decisionale

Scenario Cache consigliata Motivazione
Sviluppo locale In-Memory Semplicità, nessuna infrastruttura necessaria
Esempi/dimostrazioni In-Memory Configurazione semplice per le dimostrazioni
Produzione a server singolo (riavvii possibili) In-Memory Accettabile se le sessioni possono essere ristabilite
Produzione multiserver Redis Cache condivisa, prestazioni elevate, affidabile
applicazioni ospitate su Azure cache di Azure per Redis Integrazione Azure nativa, servizio gestito
Organizzazione locale SQL Server Sfrutta l'infrastruttura esistente
Ambienti PostgreSQL PostgreSQL Usa il database PostgreSQL esistente, semantica SQL familiare
Ambienti a sicurezza elevata SQL Server e crittografia Residenza dei dati, crittografia in stato di inattività
Test di scenari distribuiti Memoria distribuita Verifica il comportamento della cache L2 senza infrastruttura

Implementazioni della cache

1. In-Memory cache

Quando usare:

  • Sviluppo e test
  • Distribuzioni a server singolo con un comportamento di riavvio accettabile
  • Esempi e prototipi

Configuration:

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

Con opzioni personalizzate:

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

→ Altre informazioni sulla configurazione della cache in memoria


2. Cache distribuita (L2) con supporto L1 automatico

Quando usare:

  • Distribuzioni multiserver di produzione
  • Applicazioni che richiedono la persistenza della cache tra riavvii
  • Scenari di disponibilità elevata

Key Feature: Da Microsoft. Identity.Web v1.8.0, la cache distribuita include automaticamente una cache L1 in memoria per prestazioni e affidabilità.

appsettings.json:

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

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

cache di Azure per Redis

builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration.GetConnectionString("AzureRedis");
    options.InstanceName = "MyApp_";
});

Formato della stringa di connessione:

<cache-name>.redis.cache.windows.net:6380,password=<access-key>,ssl=True,abortConnect=False

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

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

Cache PostgreSQL

Richiede il pacchetto 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
  }
}

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

→ Altre informazioni sulla configurazione della cache distribuita


Attenzione

La memorizzazione nella cache basata su sessione presenta limitazioni significative:

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:

  • Problemi di dimensioni dei cookie - Token ID di grandi dimensioni con molte attestazioni causano problemi
  • conflitti di ambito Scope - non è possibile utilizzare con singleton TokenAcquisition (ad esempio, Microsoft Graph SDK)
  • Affinità di sessione richiesta : non funziona correttamente negli scenari con carico bilanciato
  • Non consigliato : usare invece la cache distribuita

Configurazione avanzata

Controllo cache L1

La cache L1 (in memoria) migliora le prestazioni quando si usano cache distribuite:

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

Quando disabilitare L1:

  • Nessuna affinità di sessione nel servizio di bilanciamento del carico
  • Gli utenti hanno spesso richiesto l'autenticazione a più fattori a causa dell'incoerenza della cache
  • Compromesso prestazionale: l'accesso a L2 è più lento (~30 ms contro ~10 ms)

Criteri di rimozione della cache

Controllare quando vengono rimossi i token memorizzati nella cache:

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

In alternativa, configurare tramite appsettings.json:

{
  "TokenCacheOptions": {
    "AbsoluteExpirationRelativeToNow": "72:00:00",
    "SlidingExpiration": "02:00:00"
  }
}
builder.Services.Configure<MsalDistributedTokenCacheAdapterOptions>(
    builder.Configuration.GetSection("TokenCacheOptions"));

Raccomandazioni:

  • Impostare la scadenza più lunga della durata del token (in genere i token scadono in 1 ora)
  • Impostazione predefinita: scadenza variabile di 90 minuti
  • Bilanciare l'utilizzo della memoria e l'esperienza utente
  • Si consideri: 72 ore assolute + 2 ore scorrevoli per un'esperienza utente valida

→ Altre informazioni sulle strategie di rimozione della cache


Crittografia di dati inattivi

Proteggere i dati dei token sensibili nelle cache distribuite:

Computer singolo

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

Sistemi distribuiti (più server)

Importante

I sistemi distribuiti non condividono le chiavi di crittografia per impostazione predefinita. È necessario configurare la condivisione delle chiavi:

Azure Key Vault (scelta consigliata):

using Microsoft.AspNetCore.DataProtection;

builder.Services.AddDataProtection()
    .PersistKeysToAzureBlobStorage(new Uri(builder.Configuration["DataProtection:BlobUri"]))
    .ProtectKeysWithAzureKeyVault(
        new Uri(builder.Configuration["DataProtection:KeyIdentifier"]),
        new DefaultAzureCredential());

Basato su certificati:

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"]));

→ Altre informazioni sulla crittografia e la protezione dei dati


Considerazioni sulle prestazioni della cache

Stime delle dimensioni dei token

Tipo di token Dimensioni tipiche Per Note
Token dell'app ~2 KB Risorsa × tenant Rimosso automaticamente
Token utente ~7 KB Utente × Tenant × Risorsa Rimozione manuale necessaria
Token di aggiornamento Variable User Di lunga durata

Pianificazione della memoria

Per 500 utenti simultanei che chiamano 3 API:

  • Token utente: 500 × 3 × 7 KB = 10,5 MB
  • Con sovraccarico: ~15-20 MB

Per 10.000 utenti simultanei:

  • Token utente: 10.000 × 3 × 7 KB = 210 MB
  • Con sovraccarico: ~300-350 MB

Raccomandazione: Impostare il limite di dimensioni della cache L1 in base agli utenti simultanei previsti.

Procedure consigliate

Usare la cache distribuita nell'ambiente di produzione - Essenziale per le distribuzioni multiserver

Impostare i limiti di dimensioni della cache appropriati - Impedire l'aumento non limitato della memoria

Configurare i criteri di rimozione - Bilanciare l'esperienza utente e l'utilizzo della memoria

Abilitare la crittografia per i dati sensibili - Proteggere i token a riposo

Monitorare l'integrità della cache - Tenere traccia delle percentuali di riscontri, degli errori e delle prestazioni

Gestire gli errori della cache L2 normalmente : la cache L1 garantisce la resilienza

Comportamento della cache di test - Verificare gli scenari di riavvio e il failover

Non usare la cache di memoria distribuita nell'ambiente di produzione : non persistente o distribuita

Non usare la cache delle sessioni : presenta limitazioni significative

Non impostare la scadenza più breve della durata del token - Forza l'autenticazione non necessaria

Non dimenticare la condivisione delle chiavi di crittografia - I sistemi distribuiti necessitano di chiavi condivise