Microsoft.Identity.Web.TokenCache NuGet 包在 Microsoft.Identity.Web 库中提供了令牌缓存序列化。
如果直接在 ASP.NET Core 应用中使用 MSAL 库,请考虑改用 Microsoft.Identity.Web,后者提供更简单、更高级别的 API。 如果未使用 MSAL 库,请参阅非 ASP.NET Core Web 应用和 Web API,其中介绍了直接 MSAL 用法。
扩展方法 |
说明 |
AddInMemoryTokenCaches |
在内存中创建临时缓存用于令牌存储和检索。 内存中令牌缓存的速度比其他缓存类型更快,但其令牌不会在应用程序重启之间持续存在,并且你无法控制缓存大小。 内存中缓存适用于不需要在应用重启之间保留令牌的应用程序。 在参与计算机到计算机身份验证方案的应用中使用内存中令牌缓存,例如服务、守护程序和其他使用 AcquireTokenForClient(客户端凭据授予)的应用程序。 内存中令牌缓存还适用于示例应用程序和本地应用开发。 Microsoft.Identity.Web 版本 1.19.0+ 在所有应用程序实例之间共享内存中令牌缓存。 |
AddSessionTokenCaches |
令牌缓存将绑定到用户会话。 如果 ID 令牌包含很多声明,此选项并不是理想的选择,因为 cookie 将会变得过大。 |
AddDistributedTokenCaches |
令牌缓存是针对 ASP.NET Core IDistributedCache 实现的适配器。 使用它可以在分布式内存缓存、Redis 缓存、分布式 NCache 或 SQL Server 缓存之间进行选择。 有关 IDistributedCache 实现的详细信息,请参阅分布式内存缓存。 |
内存中令牌缓存
下面是一个代码示例,演示了在 ASP.NET Core 应用程序中 Startup 类的 ConfigureServices 方法中使用内存中缓存:
#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
。 如果使用用户令牌,请考虑使用分布式令牌缓存。
ASP.NET Core Web 应用和 Web API 之间的令牌缓存配置代码类似。
分布式令牌缓存
下面是可能的分布式缓存示例:
// 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 'true'.
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;
});
有关详细信息,请参阅:
有关分布式缓存的使用,可查看阶段 2-2 令牌缓存中的 ASP.NET Core Web 应用教程。
即使你使用的是 MSAL.NET,也能够受益于 Microsoft.Identity.Web.TokenCache 中的令牌缓存序列化。
引用 NuGet 包
将 Microsoft.Identity.Web.TokenCache NuGet 包添加到项目中,而不是添加 MSAL.NET。
配置令牌缓存
以下代码演示如何将适当分区的内存中令牌缓存添加到应用。
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
),那么它也适用于生产应用程序,但前提是你不介意令牌缓存在 Web 应用重启后会丢失。 如果请求用户令牌(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
,则令牌缓存是针对 .NET IDistributedCache
实现的适配器。 因此,可以在 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 有一些内部代码专门用于启用与旧的 Microsoft 身份验证库 (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 包
将 Microsoft.Identity.Client.Extensions.Msal NuGet 包添加到你的项目。
配置令牌缓存
有关详细信息,请参阅 Wiki 页面。 下面是跨平台令牌缓存的用法示例:
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 子平台中提供了自定义令牌缓存序列化。 访问缓存时会触发事件。 应用可以选择是序列化还是反序列化缓存。
在处理用户操作(让用户登录并调用 Web API 的 Web 应用,以及调用下游 Web API 的 Web API)的机密客户端应用程序上,可能有很多用户。 这些用户是并行处理的。 出于安全和性能方面的原因,我们建议为每个用户序列化一个缓存。 序列化事件根据已处理用户的标识计算缓存密钥,并为该用户序列化或反序列化令牌缓存。
请记住,自定义序列化不适用于移动平台(UWP、Xamarin.iOS 和 Xamarin.Android)。 MSAL 已经为这些平台定义了安全且高效的序列化机制。 但是,.NET 桌面和 .NET Core 应用程序具有不同的体系结构。 并且 MSAL 无法实现常规用途序列化机制。
例如,网站可能会选择在 Redis 缓存中存储令牌,而桌面应用可能在加密的文件中存储令牌。 因此,提供的序列化方法并不是现成可用的。 若要在 .NET 桌面或 .NET Core 中使用持久的令牌缓存应用程序,请自定义序列化。
令牌缓存序列化中使用以下类和接口:
ITokenCache
定义用于订阅令牌缓存序列化请求的事件,以及用于以各种格式(ADAL v3.0,MSAL 2.x 和 MSAL 3.x = ADAL v5.0)对缓存进行序列化或反序列化的方法。
TokenCacheCallback
是传递给事件的回调,可让你处理序列化。 将结合 TokenCacheNotificationArgs
类型的参数调用它们。
TokenCacheNotificationArgs
仅提供应用程序的 ClientId
值,是对该令牌适用的用户的引用。

重要
MSAL.NET 为你创建令牌缓存。 当你调用应用程序的 UserTokenCache
和 AppTokenCache
属性时,它为你提供 IToken
缓存。 最好是不要自行实现接口。
实现自定义令牌缓存序列化时,你的责任是对 BeforeAccess
和 AfterAccess
事件(或其 Async
类型)做出反应。 BeforeAccess
委托负责反序列化缓存,而 AfterAccess
委托负责序列化缓存。 其中的一部分事件存储或加载 Blob,这些 Blob 将通过事件参数传递到所需的任何存储。
所用的策略会有所不同,具体取决于是针对公共客户端应用程序(桌面)还是机密客户端应用程序(Web 应用、Web API、守护程序应用)编写令牌缓存序列化。
Web 应用或 Web API(机密客户端应用程序)的自定义令牌缓存
如果你想要为机密客户端应用程序编写自己的令牌缓存序列化程序,建议从 Microsoft.Identity.Web.MsalAbstractTokenCacheProvider 继承并替代 WriteCacheBytesAsync
和 ReadCacheBytesAsync
方法。
Microsoft.Identity.Web/TokenCacheProviders 中提供了令牌缓存序列化程序的示例。
桌面或移动应用(公共客户端应用程序)的自定义令牌缓存
从 MSAL.NET v2.x 开始,可以使用多个选项来序列化公共客户端的令牌缓存。 只能将缓存序列化为 MSAL.NET 格式。 (统一格式的缓存在不同的 MSAL 和平台中是通用的。)还可支持 ADAL v3 的旧式令牌缓存序列化。
以下示例说明了如何自定义令牌缓存序列化,以在 ADAL.NET 3.x、ADAL.NET 5.x 与 MSAL.NET 之间共享单一登录状态:active-directory-dotnet-v1-to-v2。
注意
MSAL 2.x 不再支持 MSAL.NET 1.1.4-preview 令牌缓存格式。 如果你有使用 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)
);
}
}
}
}
Microsoft.Identity.Client.Extensions.Msal 开放源代码库中提供了适用于公共客户端应用程序(适用于 Windows、Mac 和 Linux 上运行的桌面应用程序)的基于文件的产品质量令牌缓存序列化程序。 可以通过以下 NuGet 包将此程序包含在应用程序中:Microsoft.Identity.Client.Extensions.Msal。
双令牌缓存序列化(MSAL 统一缓存和 ADAL v3)
如果要使用统一缓存格式(ADAL.NET 4.x、MSAL.NET 2.x 和相同平台上同一代或更低版本的其他 MSAL 通用的格式)实现令牌缓存序列化,请查看以下示例:https://github.com/Azure-Samples/active-directory-dotnet-v1-to-v2/tree/master/TokenCacheMigration/ADAL2MSAL。