MSAL.NET 中的令牌缓存序列化Token cache serialization in MSAL.NET

获取令牌后,Microsoft 身份验证库 (MSAL) 会缓存该令牌。After a token is acquired, it is cached by the Microsoft Authentication Library (MSAL). 在通过其他方法获取令牌之前,应用程序代码应该先尝试从缓存中获取令牌。Application code should try to get a token from the cache before acquiring a token by another method. 本文介绍 MSAL.NET 中令牌缓存的默认序列化和自定义序列化。This article discusses default and custom serialization of the token cache in MSAL.NET.

本文适用于 MSAL.NET 3.x。This article is for MSAL.NET 3.x. 如果你对 MSAL.NET 2.x 感兴趣,请参阅 MSAL.NET 2.x 中的令牌缓存序列化If you're interested in MSAL.NET 2.x, see Token cache serialization in MSAL.NET 2.x.

适用于移动平台的默认序列化Default serialization for mobile platforms

在 MSAL.NET 中,默认会提供内存中令牌缓存。In MSAL.NET, an in-memory token cache is provided by default. 对于可提供安全存储供用户使用的平台,默认会提供序列化。Serialization is provided by default for platforms where secure storage is available for a user as part of the platform. 这些平台包括通用 Windows 平台 (UWP)、Xamarin.iOS 和 Xamarin.Android。This is the case for Universal Windows Platform (UWP), Xamarin.iOS, and Xamarin.Android.

备注

将 Xamarin.Android 项目从 MSAL.NET 1.x 迁移到 MSAL.NET 3.x 时,可能需要将 android:allowBackup="false" 添加到项目,以避免当 Visual Studio 部署触发本地存储的还原时再次使用旧的缓存令牌。When you migrate a Xamarin.Android project from MSAL.NET 1.x to MSAL.NET 3.x, you might want to add android:allowBackup="false" to your project to avoid old cached tokens from coming back when Visual Studio deployments trigger a restore of local storage. 请参阅问题 #659See Issue #659.

适用于 Windows 桌面应用和 Web 应用/Web API 的自定义序列化Custom serialization for Windows desktop apps and web apps/web APIs

请记住,自定义序列化不适用于移动平台(UWP、Xamarin.iOS 和 Xamarin.Android)。Remember, custom serialization isn't available on mobile platforms (UWP, Xamarin.iOS, and Xamarin.Android). MSAL 已经为这些平台定义了安全且高效的序列化机制。MSAL already defines a secure and performant serialization mechanism for these platforms. 但是,.NET 桌面和 .NET Core 应用程序使用不同的体系结构,而 MSAL 无法实现通用的序列化机制。.NET desktop and .NET Core applications, however, have varied architectures and MSAL can't implement a general-purpose serialization mechanism. 例如,网站可能会选择在 Redis 缓存中存储令牌,而桌面应用在加密的文件中存储令牌。For example, web sites may choose to store tokens in a Redis cache, or desktop apps store tokens in an encrypted file. 因此,提供的序列化方法并不是按原样使用的。So serialization isn't provided out-of-the-box. 若要在 .NET 桌面或 .NET Core 中使用持久的令牌缓存应用程序,请自定义序列化。To have a persistent token cache application in .NET desktop or .NET Core, customize the serialization.

令牌缓存序列化中使用以下类和接口:The following classes and interfaces are used in token cache serialization:

  • ITokenCache,定义用于订阅令牌缓存序列化请求的事件,以及用于序列化或反序列化采用各种格式的缓存的方法(ADAL v3.0、MSAL 2.x 和 MSAL 3.x = ADAL v5.0)。ITokenCache, which defines events to subscribe to token cache serialization requests as well as methods to serialize or de-serialize the cache at various formats (ADAL v3.0, MSAL 2.x, and MSAL 3.x = ADAL v5.0).

  • TokenCacheCallback 是传递给事件的回调,可让你处理序列化。TokenCacheCallback is a callback passed to the events so that you can handle the serialization. 将结合 TokenCacheNotificationArgs 类型的参数调用它们。They'll be called with arguments of type TokenCacheNotificationArgs.

  • TokenCacheNotificationArgs 仅提供应用程序的 ClientId,是对该令牌适用的用户的引用。TokenCacheNotificationArgs only provides the ClientId of the application and a reference to the user for which the token is available.

    类图

重要

MSAL.NET 将为你创建令牌缓存,当你调用应用程序的 UserTokenCacheAppTokenCache 属性时,它会提供 IToken 缓存。MSAL.NET creates token caches for you and provides you with the IToken cache when you call an application's UserTokenCache and AppTokenCache properties. 最好是不要自行实现接口。You are not supposed to implement the interface yourself. 实现自定义令牌缓存序列化时,你的责任是:Your responsibility, when you implement a custom token cache serialization, is to:

  • 回应 BeforeAccessAfterAccess“事件”(或其异步风格)。React to BeforeAccess and AfterAccess "events" (or their Async flavors). BeforeAccess 委托负责反序列化缓存,而 AfterAccess 负责序列化缓存。The BeforeAccess delegate is responsible to deserialize the cache, whereas the AfterAccess one is responsible for serializing the cache.
  • 其中的一部分事件存储或加载 Blob,这些 Blob 将通过事件参数传递到所需的任何存储。Part of these events store or load blobs, which are passed through the event argument to whatever storage you want.

所用的策略会有所不同,具体取决于是针对公共客户端应用程序(桌面)还是机密客户端应用程序(Web 应用/ Web API、守护程序应用程序)编写令牌缓存序列化。The strategies are different depending on if you're writing a token cache serialization for a public client application (desktop), or a confidential client application) (web app / web API, daemon app).

适用于公共客户端的令牌缓存Token cache for a public client

从 MSAL.NET v2.x 开始,可以使用多个选项来序列化公共客户端的令牌缓存。Since MSAL.NET v2.x you have several options for serializing the token cache of a public client. 只能以 MSAL.NET 格式序列化缓存(统一格式的缓存在不同的 MSAL 和平台中是通用的)。You can serialize the cache only to the MSAL.NET format (the unified format cache is common across MSAL and the platforms). 还可以支持 ADAL V3 的旧式令牌缓存序列化。You can also support the legacy token cache serialization of ADAL V3.

以下示例说明了如何自定义令牌缓存序列化,以在 ADAL.NET 3.x、ADAL.NET 5.x 与 MSAL.NET 之间共享单一登录状态:active-directory-dotnet-v1-to-v2Customizing the token cache serialization to share the single sign-on state between ADAL.NET 3.x, ADAL.NET 5.x, and MSAL.NET is explained in part of the following sample: active-directory-dotnet-v1-to-v2.

备注

MSAL 2.x 不再支持 MSAL.NET 1.1.4-preview 令牌缓存格式。The MSAL.NET 1.1.4-preview token cache format is no longer supported in MSAL 2.x. 如果应用程序利用 MSAL.NET 1.x,则用户必须重新登录。If you have applications leveraging MSAL.NET 1.x, your users will have to re-sign-in. 也支持从 ADAL 4.x(和 3.x)迁移。Alternately, the migration from ADAL 4.x (and 3.x) is supported.

简单令牌缓存序列化(仅限 MSAL)Simple token cache serialization (MSAL only)

下面是适用于桌面应用程序的令牌缓存的自定义序列化的简单实现示例。Below is an example of a naive implementation of custom serialization of a token cache for desktop applications. 此处,用户令牌缓存是应用程序所在的同一文件夹中的某个文件。Here, the user token cache is a file in the same folder as the application.

生成应用程序后,通过调用 TokenCacheHelper.EnableSerialization() 方法并传递应用程序 UserTokenCache 来启用序列化。After you build the application, you enable the serialization by calling the TokenCacheHelper.EnableSerialization() method and passing the application UserTokenCache.

app = PublicClientApplicationBuilder.Create(ClientId)
    .Build();
TokenCacheHelper.EnableSerialization(app.UserTokenCache);

TokenCacheHelper 帮助器类定义为:The TokenCacheHelper helper class is defined as:

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 changesgs in the persistent store
     File.WriteAllBytes(CacheFilePath,
                         ProtectedData.Protect(args.TokenCache.SerializeMsalV3(),
                                                 null,
                                                 DataProtectionScope.CurrentUser)
                         );
    }
   }
  }
 }

Microsoft.Identity.Client.Extensions.Msal 开源库中提供了适用于公共客户端应用程序(适用于 Windows、Mac 和 Linux 上运行的桌面应用程序)的基于产品质量令牌缓存文件的序列化程序。A product quality token cache file based serializer for public client applications (for desktop applications running on Windows, Mac and Linux) is available from the Microsoft.Identity.Client.Extensions.Msal open-source library. 可以通过以下 NuGet 包将此程序包含在应用程序中:Microsoft.Identity.Client.Extensions.MsalYou can include it in your applications from the following NuGet package: Microsoft.Identity.Client.Extensions.Msal.

双令牌缓存序列化(MSAL 统一缓存和 ADAL v3)Dual token cache serialization (MSAL unified cache and ADAL v3)

若要在同一个平台上同时使用统一缓存格式(在 ADAL.NET 4.x 和 MSAL.NET 2.x 中通用)和同一代的或更低版本的其他 MSAL 实现令牌缓存序列化,请查看以下代码:If you want to implement token cache serialization both with the unified cache format (common to ADAL.NET 4.x, MSAL.NET 2.x, and other MSALs of the same generation or older, on the same platform), take a look at the following code:

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

在此情况下,帮助器类定义为:This time the helper class as defined as:

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 above, 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 un-encrypted blob (this is a json format)
  private static bool encrypt = true;
 }
}

Web 应用(机密客户端应用程序)的令牌缓存Token cache for a web app (confidential client application)

在 Web 应用或 Web API 中,缓存可以利用会话、Redis 缓存或数据库。In web apps or web APIs, the cache could leverage the session, a Redis cache, or a database. 在 Web 应用或 Web API 中,应该为每个帐户保留一个令牌缓存。You should keep one token cache per account in web apps or web APIs.

对于 Web 应用,令牌缓存应使用帐户 ID 进行键控。For web apps, the token cache should be keyed by the account ID.

对于 Web API,帐户应使用用于调用该 API 的令牌的哈希值进行键控。For web APIs, the account should be keyed by the hash of the token used to call the API.

MSAL.NET 在 .NET Framework 和 .NET Core 子平台中提供自定义令牌缓存序列化。MSAL.NET provides custom token cache serialization in .NET Framework and .NET Core subplatforms. 访问缓存时会触发事件,应用可以选择是对缓存进行序列化还是反序列化。Events are fired when the cache is accessed, apps can choose whether to serialize or deserialize the cache. 在处理用户的机密客户端应用程序(登录用户并调用 Web API 的 Web 应用程序,以及调用下游 Web API 的 Web API)中,可能会有许多用户,并且会对这些用户进行并行处理。On confidential client applications that handle users (web apps that sign in users and call web APIs, and web APIs calling downstream web APIs), there can be many users and the users are processed in parallel. 出于安全和性能方面的原因,我们建议为每个用户序列化一个缓存。For security and performance reasons, our recommendation is to serialize one cache per user. 序列化事件基于已处理用户的标识来计算缓存密钥,并对该用户的令牌缓存进行序列化/反序列化操作。Serialization events compute a cache key based on the identity of the processed user and serialize/deserialize a token cache for that user.

Microsoft.Identity.Web 库提供了一个预览版 NuGet 包 Microsoft.Identity.Web,其中包含令牌缓存序列化:The Microsoft.Identity.Web library provides a preview NuGet package Microsoft.Identity.Web containing token cache serialization:

扩展方法Extension Method Microsoft.Identity.Web 子命名空间Microsoft.Identity.Web sub namespace 说明Description
AddInMemoryTokenCaches TokenCacheProviders.InMemory 内存中令牌缓存序列化。In memory token cache serialization. 此实现在示例中非常有用。This implementation is great in samples. 如果你不介意令牌缓存在 Web 应用重启后会丢失这一情况,那么它也适用于生产应用程序。It's also good in production applications provided you don't mind if the token cache is lost when the web app is restarted. AddInMemoryTokenCaches 采用类型为 MsalMemoryTokenCacheOptions 的可选参数,该参数用于指定缓存项在未使用的情况下过期前的持续时间。AddInMemoryTokenCaches takes an optional parameter of type MsalMemoryTokenCacheOptions that enables you to specify the duration after which the cache entry will expire unless it's used.
AddSessionTokenCaches TokenCacheProviders.Session 令牌缓存将绑定到用户会话。The token cache is bound to the user session. 如果 ID 令牌包含许多声明,则此选项不是理想的选择,因为 Cookie 会变得太大。This option isn't ideal if the ID token contains many claims as the cookie would become too large.
AddDistributedTokenCaches TokenCacheProviders.Distributed 令牌缓存是针对 ASP.NET Core IDistributedCache 实现的适配器,因此允许你在分布式内存缓存、Redis 缓存、分布式 NCache 或 SQL Server 缓存之间进行选择。The token cache is an adapter against the ASP.NET Core IDistributedCache implementation, therefore enabling you to choose between a distributed memory cache, a Redis cache, a distributed NCache, or a SQL Server cache. 有关 IDistributedCache 实现的详细信息,请参阅 https://docs.microsoft.com/aspnet/core/performance/caching/distributed#distributed-memory-cacheFor details about the IDistributedCache implementations, see https://docs.microsoft.com/aspnet/core/performance/caching/distributed#distributed-memory-cache.

下面是一个示例,说明了如何在 ASP.NET Core 应用程序的 Startup 类的 ConfigureServices 方法中使用内存中缓存:Here's an example of using the in-memory cache in the ConfigureServices method of the Startup class in an ASP.NET Core application:

// or use a distributed Token Cache by adding
    services.AddSignIn(Configuration);
    services.AddWebAppCallsProtectedWebApi(Configuration, new string[] { scopesToRequest })
            .AddInMemoryTokenCaches();

可能的分布式缓存的示例:Examples of possible distributed caches:

// or use a distributed Token Cache by adding
    services.AddSignIn(Configuration);
    services.AddWebAppCallsProtectedWebApi(Configuration, new string[] { scopesToRequest })
            .AddDistributedTokenCaches();

// and then choose your implementation

// For instance the distributed in memory cache (not cleared when you stop the app)
services.AddDistributedMemoryCache()

// Or a Redis cache
services.AddStackExchangeRedisCache(options =>
{
 options.Configuration = "localhost";
 options.InstanceName = "SampleInstance";
});

// Or even a SQL Server token cache
services.AddDistributedSqlServerCache(options =>
{
 options.ConnectionString = _config["DistCache_ConnectionString"];
 options.SchemaName = "dbo";
 options.TableName = "TestCache";
});

它们的用法在 ASP.NET Core Web 应用教程2-2 令牌缓存阶段中进行了说明。Their usage is featured in the ASP.NET Core web app tutorial in the phase 2-2 Token Cache.

后续步骤Next steps

以下示例演示了令牌缓存序列化。The following samples illustrate token cache serialization.

示例Sample 平台Platform 说明Description
active-directory-dotnet-desktop-msgraph-v2active-directory-dotnet-desktop-msgraph-v2 桌面 (WPF)Desktop (WPF) 调用 Microsoft Graph API 的 Windows 桌面 .NET (WPF) 应用程序。Windows Desktop .NET (WPF) application calling the Microsoft Graph API. 关系图显示了一个拓扑,其中桌面应用 W P F TodoListClient 通过以交互方式获取令牌来流向 Azure A D,并流向 Microsoft Graph。
active-directory-dotnet-v1-to-v2active-directory-dotnet-v1-to-v2 桌面(控制台)Desktop (Console) 一组 Visual Studio 解决方案,阐释了如何将 Azure AD v1.0 应用程序(使用 ADAL.NET)迁移到 Microsoft 标识平台应用程序(使用 MSAL.NET)。Set of Visual Studio solutions illustrating the migration of Azure AD v1.0 applications (using ADAL.NET) to Microsoft identity platform applications (using MSAL.NET). 如需具体信息,请参阅令牌缓存迁移In particular, see Token Cache Migration