Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Questa guida illustra come usare la cache dei token di Microsoft.Identity.Web e i pacchetti di certificato con MSAL.NET in .NET Framework, .NET Standard 2.0 e applicazioni .NET classiche (.NET 4.7.2+).
Informazioni generali
A partire da Microsoft. Identity.Web 1.17+, è possibile usare Microsoft. Pacchetti di utilità Identity.Web con MSAL.NET in ambienti non ASP.NET Core.
Perché usare questi pacchetti?
| Feature | Beneficio |
|---|---|
| Serializzazione della cache dei token | Adattatori cache riutilizzabili per in-memory, SQL Server, Redis, Cosmos DB, PostgreSQL |
| Assistenti certificato | Caricamento semplificato dei certificati da Key Vault, file system o archivi di certificati |
| Estensioni delle attestazioni | Metodi di utilità per la manipolazione di ClaimsPrincipal |
| .NET Standard 2.0 | Compatibile con .NET Framework 4.7.2+, .NET Core e .NET 5+ |
| Dipendenze minime | Pacchetti mirati senza dipendenze da ASP.NET Core |
Scenari supportati
- .NET Framework Console Applications (scenari demone)
- Desktop Applications (.NET Framework)
- Servizi di lavoro (.NET Framework)
- librerie .NET Standard 2.0 (compatibilità multipiattaforma)
- Applicazione di MSAL.NET Non Web
Annotazioni
Per ASP.NET MVC/applicazioni API Web, vedere invece OWIN Integration.
Opzioni del pacchetto
Pacchetti principali per MSAL.NET
| Package | Scopo | Dipendenze | destinazione .NET |
|---|---|---|---|
| Microsoft. Identity.Web.TokenCache | Serializzatori della cache dei token, ClaimsPrincipal estensioni |
Minime | .NET Standard 2.0 |
| Microsoft. Identity.Web.Certificate | Utilità di caricamento dei certificati | Minime | .NET Standard 2.0 |
Installazione
Gestione pacchetti 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
Perché non Microsoft. Identity.Web (core)?
Il pacchetto core Microsoft.Identity.Web include le dipendenze di ASP.NET Core (Microsoft.AspNetCore.*), che:
- Non sono compatibili con ASP.NET Framework
- Aumentare le dimensioni del pacchetto inutilmente
- Creare conflitti di dipendenza
Usare invece pacchetti di destinazione per .NET Framework e .NET scenari Standard.
Serializzazione della cache dei token
Informazioni generali
Microsoft. Identity.Web fornisce adattatori della cache dei token che funzionano perfettamente con MSAL.NET IConfidentialClientApplication.
Modello: Creazione di client riservati con la cache dei token
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;
}
}
Opzioni della cache dei token
Opzione 1: cache dei token in memoria
Cache in memoria semplice:
using Microsoft.Identity.Web.TokenCacheProviders;
_app.AddInMemoryTokenCache();
Cache in memoria con limiti di dimensione (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
});
});
Caratteristiche:
- Accesso rapido
- Nessuna dipendenza esterna
- Non condiviso tra processi
- Perso al riavvio dell'app
Caso d'uso: App console a istanza singola, applicazioni desktop
Opzione 2: Cache dei token in memoria distribuita
Per gli ambienti a istanze multipla con cache in memoria:
_app.AddDistributedTokenCaches(services =>
{
// Requires: Microsoft.Extensions.Caching.Memory (NuGet)
services.AddDistributedMemoryCache();
});
Caratteristiche:
- Condiviso tra istanze dell'app
- Migliore per gli scenari con carico bilanciato
- Richiede un pacchetto NuGet aggiuntivo
- Ancora perso al riavvio dell'app
Caso d'uso: Servizi a istanze multipla con acquisizione di token accettabile
Opzione 3: cache dei token SQL Server
Per la memorizzazione nella cache distribuita persistente:
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);
});
});
Configurazione del database:
-- 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]);
Caratteristiche:
- Persistente attraverso i riavvii
- Condiviso tra più istanze
- Affidabile e scalabile
- Richiede SQL Server configurazione
Caso d'uso: Servizi daemon di produzione, attività pianificate, ruoli di lavoro a istanze multiple
Opzione 4: Cache dei token Redis
Per la memorizzazione nella cache distribuita a prestazioni elevate:
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_";
});
});
Configurazione di produzione:
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
};
});
Caratteristiche:
- Estremamente veloce
- Condivisi tra istanze
- Persistente (con persistenza Redis abilitata)
- Richiede il server Redis
Caso d'uso: App daemon con volumi elevati, sistemi distribuiti, microservizi
Opzione 5: Cache dei token di Cosmos DB
Per la memorizzazione nella cache distribuita a livello globale:
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;
});
});
Caratteristiche:
- Distribuito a livello globale
- A disponibilità elevata
- Scalabilità automatica
- Latenza più elevata rispetto a Redis
- Costo più elevato
Caso d'uso: Servizi daemon globali, applicazioni con distribuzione geografica
Opzione 6: Cache dei token PostgreSQL
Per la memorizzazione nella cache distribuita relazionale con 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);
});
});
Caratteristiche:
- Persistente attraverso i riavvii
- Condiviso tra più istanze
- Semantica SQL familiare
- Funziona con Database di Azure per PostgreSQL
- Richiede un server PostgreSQL
Caso d'uso: Applicazioni che già utilizzano PostgreSQL come database primario, o servizi ospitati su Azure che utilizzano Azure Database per PostgreSQL
Esempio completo: Applicazione Daemon
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
}
}
}
Gestione dei certificati
Informazioni generali
Microsoft. Identity.Web semplifica il caricamento dei certificati da varie origini per i flussi di credenziali client.
Modello: caricamento di certificati con DefaultCertificateLoader
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;
}
}
Origini certificato
1. Da Azure Key Vault
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();
Prerequisiti:
- Identità gestita o Principale del Servizio con accesso a Key Vault
- pacchetto NuGet
Azure.Identity - autorizzazione Key Vault:
Getsui certificati
2. Dall'archivio certificati
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();
Oppure trova tramite impronta digitale:
var certDescription = CertificateDescription.FromStoreWithThumbprint(
thumbprint: "ABCDEF1234567890ABCDEF1234567890ABCDEF12",
storeName: StoreName.My,
storeLocation: StoreLocation.LocalMachine
);
3. Dal file system
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();
Nota sulla sicurezza: Non memorizzare mai password nel codice. Usare la configurazione sicura.
4. Da stringa codificata in 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);
Caricamento del certificato basato sulla configurazione
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>
Codice C#:
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")
};
}
Applicazioni di esempio
Esempi ufficiali di Microsoft
| Esempio | Piattaforma | Descrizione |
|---|---|---|
| ConfidentialClientTokenCache | Console (Framework .NET) | Modelli di serializzazione della cache dei token |
| active-directory-dotnetcore-daemon-v2 | Console (.NET Core) | Caricamento di certificati da Key Vault |
Procedure consigliate
Cose da fare
1. Usare il modello singleton per 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. Impostare la scadenza appropriata della cache dei token:
// Access tokens typically expire after 1 hour
// Set cache expiration ABOVE token lifetime
options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);
3. Usare l'archiviazione sicura dei certificati:
// 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. Implementare una corretta gestione degli errori:
try
{
var result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
}
catch (MsalServiceException ex)
{
logger.Error($"Token acquisition failed. CorrelationId: {ex.CorrelationId}, ErrorCode: {ex.ErrorCode}");
throw;
}
5. Utilizzare la cache distribuita per la produzione:
// Correct for daemon services
app.AddDistributedTokenCaches(services =>
{
services.AddDistributedSqlServerCache(/* ... */);
});
Cose da non fare
1. Non creare ripetutamente nuove istanze 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. Non inserire dati sensibili nel codice:
// Wrong
.WithClientSecret("supersecretvalue123")
// Correct
.WithClientSecret(ConfigurationManager.AppSettings["AzureAd:ClientSecret"])
3. Non usare la cache in memoria per i servizi a istanze multipla:
// Wrong for services with multiple instances
app.AddInMemoryTokenCache();
// Correct - use distributed cache
app.AddDistributedTokenCaches(services =>
{
services.AddDistributedSqlServerCache(/* ... */);
});
4. Non ignorare la convalida del certificato:
// Wrong - skips validation
ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, errors) => true;
// Correct - validate certificates properly
Migrazione da ADAL.NET
Differenze principali
| Aspetto | ADAL.NET (deprecato) | MSAL.NET + Microsoft. Identity.Web |
|---|---|---|
| Ambiti | Basato sulle risorse (https://graph.microsoft.com) |
Basato sull'ambito (https://graph.microsoft.com/.default) |
| Token Cache | Serializzazione manuale richiesta | Adattatori integrati tramite metodi di estensione |
| Attestati | Caricamento manuale di X509Certificate2 |
DefaultCertificateLoader con più origini |
| Authority | Fissato durante la costruzione | Può essere sovrascritto per richiesta |
Esempio di migrazione
ADAL.NET (Old):
AuthenticationContext authContext = new AuthenticationContext(authority);
ClientCredential credential = new ClientCredential(clientId, clientSecret);
AuthenticationResult result = await authContext.AcquireTokenAsync(resource, credential);
MSAL.NET con Microsoft. Identity.Web (New):
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();