ASP.NET使用MSAL.NET对接Graph API时如何实现令牌持久化?
MSAL.NET SQL Server分布式令牌缓存不生效解决方案
你当前的写法误用了AddDistributedTokenCache扩展方法,该方法仅适配ASP.NET Core依赖注入(DI)注册的认证流程,手动构建的ConfidentialClientApplication实例无法识别该配置,因此仍默认使用内存缓存。可根据你的项目场景选择以下修改方案:
场景1:ASP.NET Core 项目(推荐)
直接走DI注册流程,不要手动构建客户端,步骤如下:
- 先安装必要NuGet包:
Microsoft.Identity.Web、Microsoft.Identity.Web.MicrosoftGraph、Microsoft.Extensions.Caching.SqlServer - 执行CLI命令创建缓存表(提前给连接字符串账号开放对应库的表创建权限):
dotnet sql-cache create "你的SQL Server连接字符串" dbo TokenCache - 在Program.cs中注册服务:
// 注册认证与MSAL服务 builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd")) .EnableTokenAcquisitionToCallDownstreamApi(new[] { "你的Graph API权限Scope" }) .AddDistributedTokenCaches(); // 注册SQL Server分布式缓存 builder.Services.AddDistributedSqlServerCache(options => { options.ConnectionString = builder.Configuration.GetConnectionString("TokenCacheConn"); options.SchemaName = "dbo"; options.TableName = "TokenCache"; options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90); });
后续直接通过DI获取ITokenAcquisition或者GraphServiceClient即可,令牌会自动写入SQL缓存。
场景2:必须手动构建ConfidentialClientApplication
手动对接分布式缓存的读写事件,代码如下:
// 先构建分布式缓存实例 var cacheServices = new ServiceCollection(); cacheServices.AddDistributedSqlServerCache(options => { options.ConnectionString = @"你的SQL连接字符串"; options.SchemaName = "dbo"; options.TableName = "TokenCache"; options.DefaultSlidingExpiration = TimeSpan.FromMinutes(90); }); var cacheProvider = cacheServices.BuildServiceProvider(); var distributedCache = cacheProvider.GetRequiredService<IDistributedCache>(); // 构建MSAL客户端 var App = ConfidentialClientApplicationBuilder.Create("客户端ID") .WithClientSecret("客户端密钥") .WithTenantId("租户ID") .WithRedirectUri("回调地址") .WithLegacyCacheCompatibility(false) .Build(); // 配置用户令牌缓存读写逻辑(用户委托流用) App.UserTokenCache.SetBeforeAccessAsync(async args => { var cacheData = await distributedCache.GetAsync(args.SuggestedCacheKey, args.CancellationToken); if (cacheData != null) { args.TokenCache.DeserializeMsalV3(cacheData); } }); App.UserTokenCache.SetAfterAccessAsync(async args => { if (args.HasStateChanged) { await distributedCache.SetAsync( args.SuggestedCacheKey, args.TokenCache.SerializeMsalV3(), new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromMinutes(90) }, args.CancellationToken); } }); // 如果是客户端凭证流,还需要配置应用令牌缓存 App.AppTokenCache.SetBeforeAccessAsync(async args => { var cacheData = await distributedCache.GetAsync(args.SuggestedCacheKey, args.CancellationToken); if (cacheData != null) { args.TokenCache.DeserializeMsalV3(cacheData); } }); App.AppTokenCache.SetAfterAccessAsync(async args => { if (args.HasStateChanged) { await distributedCache.SetAsync( args.SuggestedCacheKey, args.TokenCache.SerializeMsalV3(), new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromMinutes(90) }, args.CancellationToken); } });
注意事项
- 必须先成功调用一次AcquireToken系列方法获取到有效令牌,才会触发缓存写入,首次未获取到令牌时缓存表为空是正常现象
- 确保SQL连接字符串有对应表的读写权限,避免写入失败
- 不要修改默认的SuggestedCacheKey,MSAL已内置用户、租户、客户端等维度的隔离逻辑,避免缓存冲突
内容的提问来源于stack exchange,提问作者jamerst
相关产品推荐
相关产品推荐

