Пакет NuGet Microsoft.Identity.Web.TokenCache обеспечивает сериализацию кэша маркеров в библиотеке Microsoft.Identity.Web.
Метод расширения |
Описание |
AddInMemoryTokenCaches |
Создает временный кэш в памяти для хранения и извлечения маркеров. Кэши маркеров в памяти выполняются быстрее, чем другие типы кэша, но их маркеры не сохраняются при перезапусках приложений, и вы не можете управлять размером кэша. Кэши в памяти подходят для приложений, которые не нуждаются в сохранении маркеров при перезапусках приложений. Используйте кэш маркеров в памяти в приложениях, которые участвуют в сценариях проверки подлинности между компьютерами, таких как службы, управляющие программы и другие, использующие AcquireTokenForClient (предоставление учетных данных клиента). Кэши маркеров в памяти также подходят для образцов приложений и во время разработки локальных приложений. Microsoft.Identity.Web версий 1.19.0+ совместно использует кэш маркеров в памяти для всех экземпляров приложения. |
AddSessionTokenCaches |
Кэш маркеров привязан к сеансу пользователя. Этот вариант неидеален, если маркер идентификатора содержит много утверждений, так как файл cookie становится слишком большим. |
AddDistributedTokenCaches |
Кэш маркеров является адаптером для реализации IDistributedCache ASP.NET Core. Он позволяет выбирать между кэшем распределенной памяти, кэшем Redis, распределенным кэшем NCache или кэшем SQL Server. Дополнительные сведения о реализациях IDistributedCache см. в статье Кэш распределенной памяти. |
Кэш маркеров в памяти
Ниже приведен пример кода с использованием кэша в памяти в методе ConfigureServices класса Startup в приложении ASP.NET Core:
#using Microsoft.Identity.Web
using Microsoft.Identity.Web;
public class Startup
{
const string scopesToRequest = "user.read";
public void ConfigureServices(IServiceCollection services)
{
// code before
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration)
.EnableTokenAcquisitionToCallDownstreamApi(new string[] { scopesToRequest })
.AddInMemoryTokenCaches();
// code after
}
// code after
}
AddInMemoryTokenCaches
подходит для рабочей среды, если вы запрашиваете маркеры только для приложений. Если вы используете маркеры пользователя, рассмотрите возможность использования распределенного кэша маркеров.
Код конфигурации кэша маркеров аналогичен для веб-приложений и веб-API ASP.NET Core.
Распределенные кэши маркеров
Вот несколько примеров распределенных кэшей:
// or use a distributed Token Cache by adding
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration)
.EnableTokenAcquisitionToCallDownstreamApi(new string[] { scopesToRequest }
.AddDistributedTokenCaches();
// Distributed token caches have a L1/L2 mechanism.
// L1 is in memory, and L2 is the distributed cache
// implementation that you will choose below.
// You can configure them to limit the memory of the
// L1 cache, encrypt, and set eviction policies.
services.Configure<MsalDistributedTokenCacheAdapterOptions>(options =>
{
// Optional: Disable the L1 cache in apps that don't use session affinity
// by setting DisableL1Cache to 'false'.
options.DisableL1Cache = false;
// Or limit the memory (by default, this is 500 MB)
options.L1CacheOptions.SizeLimit = 1024 * 1024 * 1024, // 1 GB
// You can choose if you encrypt or not encrypt the cache
options.Encrypt = false;
// And you can set eviction policies for the distributed
// cache.
options.SlidingExpiration = TimeSpan.FromHours(1);
}
// Then, choose your implementation of distributed cache
// -----------------------------------------------------
// good for prototyping and testing, but this is NOT persisted and it is NOT distributed - do not use in production
services.AddDistributedMemoryCache();
// Or a Redis cache
// Requires the Microsoft.Extensions.Caching.StackExchangeRedis NuGet package
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost";
options.InstanceName = "SampleInstance";
});
// You can even decide if you want to repair the connection
// with Redis and retry on Redis failures.
services.Configure<MsalDistributedTokenCacheAdapterOptions>(options =>
{
options.OnL2CacheFailure = (ex) =>
{
if (ex is StackExchange.Redis.RedisConnectionException)
{
// action: try to reconnect or something
return true; //try to do the cache operation again
}
return false;
};
});
// Or even a SQL Server token cache
// Requires the Microsoft.Extensions.Caching.SqlServer NuGet package
services.AddDistributedSqlServerCache(options =>
{
options.ConnectionString = _config["DistCache_ConnectionString"];
options.SchemaName = "dbo";
options.TableName = "TestCache";
});
// Or an Azure Cosmos DB cache
// Requires the Microsoft.Extensions.Caching.Cosmos NuGet package
services.AddCosmosCache((CosmosCacheOptions cacheOptions) =>
{
cacheOptions.ContainerName = Configuration["CosmosCacheContainer"];
cacheOptions.DatabaseName = Configuration["CosmosCacheDatabase"];
cacheOptions.ClientBuilder = new CosmosClientBuilder(Configuration["CosmosConnectionString"]);
cacheOptions.CreateIfNotExists = true;
});
Дополнительные сведения см. в разделе:
Использование распределенного кэша описано в руководстве по веб-приложению ASP.NET Core, этап Кэш маркеров 2-2.
Даже при работе с MSAL.NET вы можете использовать сериализаторы кэша маркеров в Microsoft.Identity.Web.TokenCache.
Ссылка на пакет NuGet
Вместо MSAL.NET добавьте в свой проект пакет NuGet Microsoft.Identity.Web.TokenCache.
Настройка кэша маркеров
В следующем коде показано, как добавить в приложение размещаемый в памяти тщательно секционированный кэш маркеров.
using Microsoft.Identity.Web;
using Microsoft.Identity.Client;
using Microsoft.Extensions.DependencyInjection;
public static async Task<AuthenticationResult> GetTokenAsync(string clientId, X509Certificate cert, string authority, string[] scopes)
{
// Create the confidential client application
app= ConfidentialClientApplicationBuilder.Create(clientId)
// Alternatively to the certificate, you can use .WithClientSecret(clientSecret)
.WithCertificate(cert)
.WithLegacyCacheCompatibility(false)
.WithAuthority(authority)
.Build();
// Add a static in-memory token cache. Other options available: see below
app.AddInMemoryTokenCache(); // Microsoft.Identity.Web.TokenCache 1.17+
// Make the call to get a token for client_credentials flow (app-to-app scenario)
return await app.AcquireTokenForClient(scopes).ExecuteAsync();
// OR Make the call to get a token for OBO (web API scenario)
return await app.AcquireTokenOnBehalfOf(scopes, userAssertion).ExecuteAsync();
// OR Make the call to get a token via auth code (web app scenario)
return await app.AcquireTokenByAuthorizationCode(scopes, authCode);
// OR, when the user has previously logged in, get a token silently
string homeAccountId = User.GetHomeAccountId(); // uid and utid claims
var account = await app.GetAccountAsync(homeAccountId);
try
{
return await app.AcquireTokenSilent(scopes, account).ExecuteAsync();;
}
catch (MsalUiRequiredException)
{
// cannot get a token silently, so redirect the user to be challenged
}
}
Доступные технологии кэширования
Вместо app.AddInMemoryTokenCache();
можно использовать различные технологии сериализации кэша. Например, можно использовать хранилище кэша маркеров без сериализации, в памяти и распределенное, предоставляемое .NET.
Кэш маркеров без сериализации
Вы можете указать, что вы не хотите использовать сериализацию кэша маркеров и вместо этого полагаться на внутренний кэш MSAL.NET:
- Используется
.WithCacheOptions(CacheOptions.EnableSharedCacheOptions)
при создании приложения.
- Не добавляйте сериализаторы.
// Create the confidential client application
app= ConfidentialClientApplicationBuilder.Create(clientId)
// Alternatively to the certificate, you can use .WithClientSecret(clientSecret)
.WithCertificate(cert)
.WithLegacyCacheCompatibility(false)
.WithCacheOptions(CacheOptions.EnableSharedCacheOptions)
.WithAuthority(authority)
.Build();
WithCacheOptions(CacheOptions.EnableSharedCacheOptions)
делает внутренний кэш маркеров MSAL общим для экземпляров клиентского приложения MSAL. Совместное использование кэша маркеров выполняется быстрее, чем при сериализации кэша маркеров, но внутренний кэш в памяти не имеет политик вытеснения. Существующие маркеры будут обновлены на месте, но при получении маркеров для разных пользователей, клиентов и ресурсов кэш увеличивается соответствующим образом.
При использовании этого подхода и наличии большого количества пользователей или клиентов следует следить за объемом занимаемой памяти. Если объем занимаемой памяти станет проблемой, рекомендуется включить сериализацию кэша маркеров, что может уменьшить размер внутреннего кэша. Также имейте в виду, что в настоящее время нельзя использовать общий кэш и сериализацию кэша вместе.
Кэш маркеров в памяти
Сериализация кэша маркеров, размещаемого в памяти, отлично работает в примерах. Она также хорошо подходит для рабочих приложений, если вы запрашиваете только маркеры приложений (AcquireTokenForClient
), при условии, что вы не против того, что при перезапуске веб-приложения кэш маркеров будет потерян. Мы не рекомендуем использовать его в рабочей среде, если вы запрашиваете маркеры пользователя (AcquireTokenByAuthorizationCode
, AcquireTokenSilent
, AcquireTokenOnBehalfOf
).
// Add an in-memory token cache
app.AddInMemoryTokenCache();
Кроме того, можно указать параметры для ограничения размера кэша маркеров в памяти:
// Add an in-memory token cache with options
app.AddInMemoryTokenCache(services =>
{
// Configure the memory cache options
services.Configure<MemoryCacheOptions>(options =>
{
options.SizeLimit = 500 * 1024 * 1024; // in bytes (500 MB)
});
}
);
Распределенные кэши
Если вы используете app.AddDistributedTokenCache
, кэш маркеров является адаптером для реализации IDistributedCache
.NET. Таким образом, вы можете выбрать кэш SQL Server, кэш Redis, кэш Azure Cosmos DB или любой другой кэш, реализовав интерфейс IDistributedCache.
Возможно, только в целях тестирования вы захотите использовать services.AddDistributedMemoryCache()
, реализацию IDistributedCache
в памяти.
Ниже приведен код для кэша SQL Server:
// SQL Server token cache
app.AddDistributedTokenCache(services =>
{
services.AddDistributedSqlServerCache(options =>
{
// Requires to reference Microsoft.Extensions.Caching.SqlServer
options.ConnectionString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=TestCache;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
options.SchemaName = "dbo";
options.TableName = "TestCache";
// You don't want the SQL token cache to be purged before the access token has expired. Usually
// access tokens expire after 1 hour (but this can be changed by token lifetime policies), whereas
// the default sliding expiration for the distributed SQL database is 20 mins.
// Use a value above 60 mins (or the lifetime of a token in case of longer-lived tokens)
options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90);
});
});
Ниже приведен код для кэша Redis:
// Redis token cache
app.AddDistributedTokenCache(services =>
{
// Requires to reference Microsoft.Extensions.Caching.StackExchangeRedis
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost";
options.InstanceName = "Redis";
});
// You can even decide if you want to repair the connection
// with Redis and retry on Redis failures.
services.Configure<MsalDistributedTokenCacheAdapterOptions>(options =>
{
options.OnL2CacheFailure = (ex) =>
{
if (ex is StackExchange.Redis.RedisConnectionException)
{
// action: try to reconnect or something
return true; //try to do the cache operation again
}
return false;
};
});
});
Ниже приведен код для кэша Azure Cosmos DB:
// Azure Cosmos DB token cache
app.AddDistributedTokenCache(services =>
{
// Requires to reference Microsoft.Extensions.Caching.Cosmos
services.AddCosmosCache((CosmosCacheOptions cacheOptions) =>
{
cacheOptions.ContainerName = Configuration["CosmosCacheContainer"];
cacheOptions.DatabaseName = Configuration["CosmosCacheDatabase"];
cacheOptions.ClientBuilder = new CosmosClientBuilder(Configuration["CosmosConnectionString"]);
cacheOptions.CreateIfNotExists = true;
});
});
Дополнительные сведения о распределенных кэшах см. в следующих статьях:
Отключение устаревшего кэша маркеров
В MSAL имеется специальный внутренний код, предназначенный для обеспечения взаимодействия с устаревшим кэшем библиотеки проверки подлинности Майкрософт (ADAL). Если MSAL и ADAL не используются параллельно, устаревший кэш также не используется, и соответствующий код для устаревшего кэша не требуется. В MSAL 4.25.0 добавлена возможность отключить устаревший код кэша ADAL и повысить производительность использования кэша. См. запрос на вытягивание GitHub 2309, где приведено сравнение производительности до и после отключения устаревшего кэша.
Вызовите .WithLegacyCacheCompatibility(false)
в построителе приложений, как показано в следующем коде.
var app = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithClientSecret(clientSecret)
.WithLegacyCacheCompatibility(false)
.Build();
Примеры
В классических приложениях рекомендуется использовать кроссплатформенный кэш маркеров. MSAL.NET предоставляет кроссплатформенный кэш маркеров в отдельной библиотеке Microsoft.Identity.Client.Extensions.Msal.
Ссылка на пакет NuGet
Добавьте в проект пакет NuGet Microsoft.Identity.Client.Extensions.Msal.
Настройка кэша маркеров
Дополнительные сведения см. на вики-странице. Ниже приведен пример использования кроссплатформенного кэша маркеров.
var storageProperties =
new StorageCreationPropertiesBuilder(Config.CacheFileName, Config.CacheDir)
.WithLinuxKeyring(
Config.LinuxKeyRingSchema,
Config.LinuxKeyRingCollection,
Config.LinuxKeyRingLabel,
Config.LinuxKeyRingAttr1,
Config.LinuxKeyRingAttr2)
.WithMacKeyChain(
Config.KeyChainServiceName,
Config.KeyChainAccountName)
.Build();
IPublicClientApplication pca = PublicClientApplicationBuilder.Create(clientId)
.WithAuthority(Config.Authority)
.WithRedirectUri("http://localhost") // make sure to register this redirect URI for the interactive login
.Build();
// This hooks up the cross-platform cache into MSAL
var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties );
cacheHelper.RegisterCache(pca.UserTokenCache);
Резервный режим обычного текста
Кроссплатформенный кэш маркеров позволяет хранить незашифрованные маркеры в виде обычного текста. Эта функция предназначена только для использования в средах разработки только в целях отладки.
Резервный режим обычного текста можно использовать с помощью приведенного ниже шаблона кода.
storageProperties =
new StorageCreationPropertiesBuilder(
Config.CacheFileName + ".plaintext",
Config.CacheDir)
.WithUnprotectedFile()
.Build();
var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties).ConfigureAwait(false);
MSAL.NET предоставляет кэш маркеров в памяти по умолчанию. Сериализация по умолчанию работает на всех платформах, которые предоставляют пользователю безопасное хранилище: универсальная платформа Windows (UWP), Xamarin.iOS и Xamarin.Android.
Если вы хотите создать собственный сериализатор кэша маркеров, MSAL.NET обеспечивает настраиваемую сериализацию для платформы .NET Framework и подплатформ .NET Core. События вызываются при обращении к кэшу. Приложения могут выбрать, следует ли выполнять сериализацию или десериализовать кэша.
В конфиденциальных клиентских приложениях, которые обслуживают пользователей (веб-приложения, которые выполняют для пользователей вход и вызывают веб-API, а также веб-API, вызывающие нисходящие веб-API), может быть много пользователей. Пользователи обрабатываются параллельно. По соображениям безопасности и производительности мы рекомендуем сериализовать по одному кэшу на пользователя. События сериализации вычисляют ключ кэша на основе удостоверения обрабатываемого пользователя и выполняют для него сериализацию или десериализацию кэша маркеров.
Помните, что пользовательская сериализация не поддерживается на мобильных платформах (UWP, Xamarin.iOS и Xamarin.Android). MSAL уже предоставляет безопасный и эффективный механизм сериализации для этих платформ. Однако приложения .NET для настольных компьютеров и .NET Core имеют различные архитектуры. И MSAL не может реализовать механизм сериализации общего назначения.
Например, веб-сайты могут хранить маркеры в кэше Redis, а классические приложения — в зашифрованном файле. Поэтому сериализация в стандартной конфигурации не предоставляется. Чтобы приложение .NET или .NET Core могло использовать постоянный кэш маркеров, настройте сериализацию.
Для сериализации кэша маркеров используются следующие классы и интерфейсы:
ITokenCache
определяет события для подписки на запросы сериализации кэша маркеров, а также методы для сериализации и десериализации кэша в разных форматах (ADAL версии 3.0, MSAL 2.x и MSAL 3.x = ADAL версии 5.0).
- Обратный вызов
TokenCacheCallback
передается событиям, что позволяет обрабатывать операции сериализации. Они будут вызываться с аргументами типа TokenCacheNotificationArgs
.
TokenCacheNotificationArgs
предоставляет только ClientId
приложения и ссылку на пользователя, для которого доступен маркер.

Важно!
MSAL.NET создает кэши маркеров. Он предоставляет кэш IToken
при вызове свойств UserTokenCache
и AppTokenCache
приложения. Вам не нужно самостоятельно реализовать этот интерфейс.
При реализации пользовательской сериализации для кэша маркеров в вашу зону ответственности входит реагирование на события BeforeAccess
и AfterAccess
(или их разновидности Async
). Делегат BeforeAccess
отвечает за десериализацию кэша, а AfterAccess
выполняет сериализацию кэша. Часть этих событий сохраняют или загружают большие двоичные объекты, которые передаются через аргумент события в нужное хранилище.
Стратегии будут разными в зависимости от того, выполняется ли сериализация кэша маркеров для общедоступного клиентского приложения (классическое приложение) или конфиденциального клиентского приложения (веб-приложение, веб-API, управляющая программа).
Пользовательский кэш маркеров для веб-приложения или веб-API (конфиденциального клиентского приложения)
Если вы хотите написать собственный сериализатор кэша маркеров для конфиденциальных клиентских приложений, рекомендуется наследовать его от Microsoft.Identity.Web.MsalAbstractTokenCacheProvider и переопределить методы WriteCacheBytesAsync
и ReadCacheBytesAsync
.
Примеры сериализаторов кэша маркеров приведены в Microsoft.Identity.Web/TokenCacheProviders.
Пользовательский кэш маркеров для классического или мобильного приложения (общедоступное клиентское приложение)
Начиная с версии MSAL.NET 2.x у вас есть несколько способов для сериализации кэша маркеров в общедоступном клиенте. Кэш можно сериализовать только в формате MSAL.NET. (Кэш единого формата часто используется в MSAL и на платформах.) Вы также можете поддерживать сериализацию кэша маркеров прежних версий для ADAL версии 3.
Настройка сериализации кэша маркера для совместного использования состояния единого входа между ADAL.NET 3.x, ADAL.NET 5.x и MSAL.NET описана в одном из разделов следующего примера: active-directory-dotnet-v1-to-v2.
Примечание
Формат кэша маркеров MSAL.NET версии 1.1.4-preview уже не поддерживается в MSAL 2.x. Если у вас есть приложения, которые используют MSAL.NET 1.x, пользователям придется выполнять вход повторно. Поддерживается миграция из ADAL 4.x (и версии 3.x).
Простая сериализация кэша маркеров (только для MSAL)
Следующий код представляет собой пример основной реализации пользовательской сериализации кэша маркеров для классических приложений. Здесь в качестве кэша для маркеров пользователей применяется файл, расположенный в одной папке с приложением.
После сборки приложения вы можете включить сериализацию, вызвав метод TokenCacheHelper.EnableSerialization()
и передав ему свойство UserTokenCache
приложения.
app = PublicClientApplicationBuilder.Create(ClientId)
.Build();
TokenCacheHelper.EnableSerialization(app.UserTokenCache);
Вспомогательный класс TokenCacheHelper
определен следующим образом:
static class TokenCacheHelper
{
public static void EnableSerialization(ITokenCache tokenCache)
{
tokenCache.SetBeforeAccess(BeforeAccessNotification);
tokenCache.SetAfterAccess(AfterAccessNotification);
}
/// <summary>
/// Path to the token cache. Note that this could be something different, for instance, for MSIX applications:
/// private static readonly string CacheFilePath =
/// $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\{AppName}\msalcache.bin";
/// </summary>
public static readonly string CacheFilePath = System.Reflection.Assembly.GetExecutingAssembly().Location + ".msalcache.bin3";
private static readonly object FileLock = new object();
private static void BeforeAccessNotification(TokenCacheNotificationArgs args)
{
lock (FileLock)
{
args.TokenCache.DeserializeMsalV3(File.Exists(CacheFilePath)
? ProtectedData.Unprotect(File.ReadAllBytes(CacheFilePath),
null,
DataProtectionScope.CurrentUser)
: null);
}
}
private static void AfterAccessNotification(TokenCacheNotificationArgs args)
{
// if the access operation resulted in a cache update
if (args.HasStateChanged)
{
lock (FileLock)
{
// reflect changes in the persistent store
File.WriteAllBytes(CacheFilePath,
ProtectedData.Protect(args.TokenCache.SerializeMsalV3(),
null,
DataProtectionScope.CurrentUser)
);
}
}
}
}
Сериализатор для производственных сред с файлом кэша маркеров, предназначенный для общедоступных клиентских приложений (классических приложений под управлением Windows, Mac и Linux), вы можете получить в библиотеке открытого кода Microsoft.Identity.Client.Extensions.Msal. Это решение можно включить в приложение из следующего пакета NuGet: Microsoft.Identity.Client.Extensions.Msal.
Сериализация сдвоенного кэша маркеров (единого кэша для MSAL и ADAL версии 3)
Если вы хотите реализовать сериализацию кэша маркеров для единого формата кэша (для ADAL.NET 4.x, MSAL.NET 2.x и для других MSAL того же поколения или более старых на той же платформе), изучите следующий код:
string appLocation = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location;
string cacheFolder = Path.GetFullPath(appLocation) + @"..\..\..\..");
string adalV3cacheFileName = Path.Combine(cacheFolder, "cacheAdalV3.bin");
string unifiedCacheFileName = Path.Combine(cacheFolder, "unifiedCache.bin");
IPublicClientApplication app;
app = PublicClientApplicationBuilder.Create(clientId)
.Build();
FilesBasedTokenCacheHelper.EnableSerialization(app.UserTokenCache,
unifiedCacheFileName,
adalV3cacheFileName);
В этом случае вспомогательный класс определяется следующим образом:
using System;
using System.IO;
using System.Security.Cryptography;
using Microsoft.Identity.Client;
namespace CommonCacheMsalV3
{
/// <summary>
/// Simple persistent cache implementation of the dual cache serialization (ADAL v3 legacy
/// and unified cache format) for a desktop applications (from MSAL 2.x)
/// </summary>
static class FilesBasedTokenCacheHelper
{
/// <summary>
/// Enables the serialization of the token cache
/// </summary>
/// <param name="adalV3CacheFileName">File name where the cache is serialized with the
/// ADAL v3 token cache format. Can
/// be <c>null</c> if you don't want to implement the legacy ADAL v3 token cache
/// serialization in your MSAL 2.x+ application</param>
/// <param name="unifiedCacheFileName">File name where the cache is serialized
/// with the unified cache format, common to
/// ADAL v4 and MSAL v2 and later, and also across ADAL/MSAL on the same platform.
/// Should not be <c>null</c></param>
/// <returns></returns>
public static void EnableSerialization(ITokenCache tokenCache, string unifiedCacheFileName, string adalV3CacheFileName)
{
UnifiedCacheFileName = unifiedCacheFileName;
AdalV3CacheFileName = adalV3CacheFileName;
tokenCache.SetBeforeAccess(BeforeAccessNotification);
tokenCache.SetAfterAccess(AfterAccessNotification);
}
/// <summary>
/// File path where the token cache is serialized with the unified cache format
/// (ADAL.NET v4, MSAL.NET v3)
/// </summary>
public static string UnifiedCacheFileName { get; private set; }
/// <summary>
/// File path where the token cache is serialized with the legacy ADAL v3 format
/// </summary>
public static string AdalV3CacheFileName { get; private set; }
private static readonly object FileLock = new object();
public static void BeforeAccessNotification(TokenCacheNotificationArgs args)
{
lock (FileLock)
{
args.TokenCache.DeserializeAdalV3(ReadFromFileIfExists(AdalV3CacheFileName));
try
{
args.TokenCache.DeserializeMsalV3(ReadFromFileIfExists(UnifiedCacheFileName));
}
catch(Exception ex)
{
// Compatibility with the MSAL v2 cache if you used one
args.TokenCache.DeserializeMsalV2(ReadFromFileIfExists(UnifiedCacheFileName));
}
}
}
public static void AfterAccessNotification(TokenCacheNotificationArgs args)
{
// if the access operation resulted in a cache update
if (args.HasStateChanged)
{
lock (FileLock)
{
WriteToFileIfNotNull(UnifiedCacheFileName, args.TokenCache.SerializeMsalV3());
if (!string.IsNullOrWhiteSpace(AdalV3CacheFileName))
{
WriteToFileIfNotNull(AdalV3CacheFileName, args.TokenCache.SerializeAdalV3());
}
}
}
}
/// <summary>
/// Read the content of a file if it exists
/// </summary>
/// <param name="path">File path</param>
/// <returns>Content of the file (in bytes)</returns>
private static byte[] ReadFromFileIfExists(string path)
{
byte[] protectedBytes = (!string.IsNullOrEmpty(path) && File.Exists(path))
? File.ReadAllBytes(path) : null;
byte[] unprotectedBytes = encrypt ?
((protectedBytes != null) ? ProtectedData.Unprotect(protectedBytes, null, DataProtectionScope.CurrentUser) : null)
: protectedBytes;
return unprotectedBytes;
}
/// <summary>
/// Writes a blob of bytes to a file. If the blob is <c>null</c>, deletes the file
/// </summary>
/// <param name="path">path to the file to write</param>
/// <param name="blob">Blob of bytes to write</param>
private static void WriteToFileIfNotNull(string path, byte[] blob)
{
if (blob != null)
{
byte[] protectedBytes = encrypt
? ProtectedData.Protect(blob, null, DataProtectionScope.CurrentUser)
: blob;
File.WriteAllBytes(path, protectedBytes);
}
else
{
File.Delete(path);
}
}
// Change if you want to test with an unencrypted blob (this is a JSON format)
private static bool encrypt = true;
}
}
Приведенные ниже примеры демонстрируют сериализацию кэша маркеров.