Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этом руководстве показано, как использовать кэш токенов и пакеты сертификатов Microsoft.Identity.Web с MSAL.NET в .NET Framework, .NET Standard 2.0 и классических приложениях .NET (.NET 4.7.2+).
Понимание общих сведений
Начиная с Microsoft.Identity.Web 1.17+, можно использовать служебные пакеты Microsoft.Identity.Web с MSAL.NET в средах, отличных от ASP.NET Core.
Определение преимуществ пакета
| Функция | Преимущество |
|---|---|
| Сериализация кэша токенов | Многократно используемые адаптеры кэша для оперативной памяти, SQL Server, PostgreSQL, Redis, Cosmos DB |
| Вспомогательные службы сертификатов | Упрощенная загрузка сертификатов из KeyVault, файловой системы или хранилища сертификатов |
| Расширения запросов | Служебные методы для обработки ClaimsPrincipal |
| .NET Standard 2.0 | Совместим с .NET Framework 4.7.2+, .NET Core и .NET 5+ |
| Минимальные зависимости | Целевые пакеты без зависимостей ASP.NET Core |
Просмотр поддерживаемых сценариев
Следующие сценарии поддерживаются с целевыми пакетами служебной программы.
- Консольные приложения платформы .NET Framework (демон-сценарии)
- Desktop Applications (.NET Framework)
- Worker Services (.NET Framework)
- .NET Стандартные библиотеки 2.0 (кроссплатформенная совместимость)
- Независимые от веб приложения MSAL.NET
Замечание
Для приложений ASP.NET MVC/веб-API см. OWIN Integration.
Выбор пакетов
Выберите пакет, соответствующий вашему сценарию.
Определение основных пакетов для MSAL.NET
| Package | Purpose | Зависимости | целевой объект .NET |
|---|---|---|---|
| Microsoft. Identity.Web.TokenCache | Сериализаторы кэша токенов, ClaimsPrincipal расширения |
Минимальный | .NET standard 2.0 |
| Microsoft. Identity.Web.Certificate | Служебные программы загрузки сертификатов | Минимальный | .NET standard 2.0 |
Установка пакетов
Используйте один из следующих методов, чтобы добавить пакеты в проект.
диспетчер пакетов 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
Общие сведения об ограничениях основных пакетов
Основной пакет Microsoft.Identity.Web включает зависимости ASP.NET Core (Microsoft.AspNetCore.*), которые:
- Несовместимы с ASP.NET Framework
- Увеличение размера пакета ненужным образом
- Создание конфликтов зависимостей
Используйте целевые пакеты для сценариев .NET Framework и .NET Standard.
Настройка сериализации кэша токенов
Понимание кэш-адаптеров токенов
Microsoft.Identity.Web предоставляет адаптеры кэша токенов, которые легко работают с IConfidentialClientApplication MSAL.NET.
Создание конфиденциального клиента с кэшем маркеров
В следующем примере создается конфиденциальное клиентское приложение и подключается кэш маркеров в памяти.
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;
}
}
Выберите параметры кэша токенов
Выберите поставщик кэша, который лучше всего подходит для сценария развертывания.
Настройка кэша токенов в памяти
В следующем примере добавляется простой кэш в памяти:
using Microsoft.Identity.Web.TokenCacheProviders;
_app.AddInMemoryTokenCache();
Кэш в памяти с ограничениями размера (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
});
});
Характеристики.
- Быстрый доступ
- Нет внешних зависимостей
- Не разделяется между процессами
- Потеряно при перезапуске приложения
Вариант использования: Одноэкземплярные консольные приложения, настольные приложения
Настройка распределенного кэша маркеров в памяти
Используйте следующий код для добавления распределенного кэша в памяти для сред с несколькими экземплярами:
_app.AddDistributedTokenCaches(services =>
{
// Requires: Microsoft.Extensions.Caching.Memory (NuGet)
services.AddDistributedMemoryCache();
});
Характеристики.
- Разделено между экземплярами приложения
- Лучше подходит для сценариев балансировки нагрузки
- Требуется дополнительный пакет NuGet
- Данные по-прежнему теряются при перезапуске приложения
Вариант использования: Службы с несколькими экземплярами с приемлемым получением токена
Настройка кэша маркеров SQL Server
Используйте следующий код для добавления постоянного распределенного SQL Server кэша:
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);
});
});
Выполните следующий SQL, чтобы создать требуемую таблицу кэша:
-- 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]);
Характеристики.
- Сохраняется при перезапусках
- Общий для нескольких экземпляров
- Надежная и масштабируемая
- Требуется настройка SQL Server
Вариант использования: Производственные демоны сервиса, запланированные задачи, многопоточные рабочие процессы
Настройка кэша маркеров Redis
Используйте следующий код, чтобы добавить высокопроизводительный распределенный кэш Redis:
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_";
});
});
В следующем примере показана конфигурация Redis, готовая к работе.
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
};
});
Характеристики.
- Очень быстро
- Общий доступ между экземплярами
- Постоянный (с включенной сохраняемостью Redis)
- Требуется сервер Redis
Вариант использования: демон-приложения с высокой нагрузкой, распределенные системы, микросервисы
Настройка кэша маркеров Cosmos DB
Используйте следующий код для добавления глобально распределенного кэша Cosmos DB:
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;
});
});
Характеристики.
- Глобально распределенное
- Высокая доступность
- Автоматическое масштабирование
- Более высокая задержка, чем Redis
- Более высокая стоимость
Вариант использования: Глобальные службы управляющей программы, географически распределенные приложения
Настройка кэша токенов PostgreSQL
Используйте следующий код для добавления распределенного кэша 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);
});
});
Характеристики.
- Сохраняется при перезапусках
- Общий для нескольких экземпляров
- Знакомая семантика SQL
- Работает с База данных Azure для PostgreSQL
- Требуется сервер PostgreSQL
Use case: Приложения, которые уже используют PostgreSQL в качестве основной базы данных, или службы, размещенные на Azure, использующие База данных Azure для PostgreSQL
Создание полного приложения управляющей программы
В следующем примере показано полное демон-приложение, которое получает токены с помощью учетных данных клиента и кэша токенов SQL Server.
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
}
}
}
Управление сертификатами
Общие сведения о загрузке сертификата
Microsoft. Identity.Web упрощает загрузку сертификатов из различных источников для потоков учетных данных клиента.
Загрузка сертификатов с помощью DefaultCertificateLoader
В следующем примере показано, как загрузить сертификат из Azure Key Vault и создать конфиденциальное клиентское приложение.
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;
}
}
Выбор источников сертификатов
Загрузка из Azure Key Vault
Загрузите сертификат, хранящийся в Azure Key Vault, указав URL-адрес хранилища и имя сертификата.
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();
Необходимые условия:
- Управляемое удостоверение или служебный принципал с доступом к Кей Волт
- пакет NuGet
Azure.Identity - разрешение Key Vault:
Getдля сертификатов
Загрузка из хранилища сертификатов
Загрузите сертификат из хранилища сертификатов Windows по отличительному имени.
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();
Вы также можете найти сертификат по отпечатку:
var certDescription = CertificateDescription.FromStoreWithThumbprint(
thumbprint: "ABCDEF1234567890ABCDEF1234567890ABCDEF12",
storeName: StoreName.My,
storeLocation: StoreLocation.LocalMachine
);
Загрузка из файловой системы
Загрузите сертификат из PFX-файла в локальной файловой системе.
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();
Примечание по безопасности: Никогда не закодировать пароли. Используйте безопасную конфигурацию.
Загрузка из строки в кодировке Base64
Загрузите сертификат из строки в кодировке 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);
Настройка загрузки сертификатов из App.config
Определите параметры сертификата в файле App.config и загрузите их во время выполнения.
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>
Используйте следующий вспомогательный метод для загрузки сертификата на основе конфигурации:
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")
};
}
Изучение примеров приложений
Просмотрите эти примеры, чтобы просмотреть рабочие реализации.
Просмотр официальных примеров Microsoft
В следующей таблице перечислены официальные примеры, демонстрирующие кэширование маркеров и загрузку сертификатов.
| Образец | Platform | Описание |
|---|---|---|
| ConfidentialClientTokenCache | Консоль (.NET Framework) | Шаблоны сериализации кэша токенов |
| active-directory-dotnetcore-daemon-v2 | Консоль (.NET Core) | Загрузка сертификата из Key Vault |
Следуйте лучшим практикам
Примените эти шаблоны для создания надежных и безопасных приложений.
Следуйте рекомендуемым шаблонам
1. Используйте одноэлементный шаблон для 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. Установите соответствующий срок действия кэша маркеров:
Настройте время динамического истечения, превышающее срок действия маркера, чтобы избежать ненужного повторного получения.
// Access tokens typically expire after 1 hour
// Set cache expiration ABOVE token lifetime
options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);
3. Используйте безопасное хранилище сертификатов:
Храните сертификаты в Azure Key Vault или правильно защищенном хранилище сертификатов.
// 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. Реализуйте правильную обработку ошибок:
Перехват исключений MSAL и ведение журнала идентификатора корреляции для устранения неполадок.
try
{
var result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
}
catch (MsalServiceException ex)
{
logger.Error($"Token acquisition failed. CorrelationId: {ex.CorrelationId}, ErrorCode: {ex.ErrorCode}");
throw;
}
5. Использование распределенного кэша для рабочей среды:
Распределенный кэш обменивается токенами между экземплярами и сохраняет их после перезапусков.
// Correct for daemon services
app.AddDistributedTokenCaches(services =>
{
services.AddDistributedSqlServerCache(/* ... */);
});
Избегайте распространенных ошибок
1. Не создавайте экземпляры 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. Не задавайте секреты жесткого кода:
// Wrong
.WithClientSecret("supersecretvalue123")
// Correct
.WithClientSecret(ConfigurationManager.AppSettings["AzureAd:ClientSecret"])
3. Не используйте кэш в памяти для служб с несколькими экземплярами:
// Wrong for services with multiple instances
app.AddInMemoryTokenCache();
// Correct - use distributed cache
app.AddDistributedTokenCaches(services =>
{
services.AddDistributedSqlServerCache(/* ... */);
});
4. Не игнорируйте проверку сертификата:
// Wrong - skips validation
ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, errors) => true;
// Correct - validate certificates properly
Миграция из ADAL.NET
Просмотрите основные различия и обновите код, чтобы использовать MSAL.NET с Microsoft. Identity.Web.
Общие сведения о ключевых различиях
| Аспект | ADAL.NET (не рекомендуется) | MSAL.NET + Microsoft. Identity.Web |
|---|---|---|
| Области применения | На основе ресурсов (https://graph.microsoft.com) |
На основе области (https://graph.microsoft.com/.default) |
| Кэш токенов | Требуется сериализация вручную | Встроенные адаптеры с помощью методов расширения |
| Certificates | Загрузка вручную X509Certificate2 |
DefaultCertificateLoader с несколькими источниками |
| Авторитет | Исправлено на этапе строительства | Может быть переопределено для каждого запроса |
Сравнение примеров миграции
ADAL.NET (Старое):
AuthenticationContext authContext = new AuthenticationContext(authority);
ClientCredential credential = new ClientCredential(clientId, clientSecret);
AuthenticationResult result = await authContext.AcquireTokenAsync(resource, credential);
MSAL.NET с 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();
Изучение связанного содержимого
Используйте эти ресурсы, чтобы узнать больше о связанных сценариях.