ASP.NET Core 8中MSAL+Redis+Keycloak令牌缓存报错求助
问题解决:MSAL结合Redis缓存令牌时首次调用报错
场景与问题
在ASP.NET Core 8应用中,使用Microsoft.Identity.Client(v4.59)的ConfidentialClientApplicationBuilder结合Redis缓存访问令牌,身份提供商为Keyloak。
Redis配置代码
services.AddDistributedTokenCaches(); services.AddStackExchangeRedisCache(options => { options.Configuration = redisSettings.ConnectionString; options.InstanceName = redisSettings.InstanceName; });
获取令牌代码
var app = ConfidentialClientApplicationBuilder .Create("myClientId") .WithClientSecret("myClientSecret") .WithExperimentalFeatures() .WithGenericAuthority("https://mykeycloakinstance/auth/realms/master/") .Build(); // _cacheProvider是注入的IMsalTokenCacheProvider(Redis实现) _cacheProvider.Initialize(app.UserTokenCache); _cacheProvider.Initialize(app.AppTokenCache); var token = await app.AcquireTokenForClient(_scopes).ExecuteAsync(); return token.AccessToken;
问题现象
首次调用获取令牌时抛出Microsoft.Identity.Client.MsalClientException,错误码为combined_user_app_cache_not_supported,但Redis中会创建对应的令牌条目;第二次调用可正常返回缓存的令牌,缓存过期后会重复该问题(首次报错、二次成功)。
解决方案
1. 移除不必要的用户令牌缓存初始化
你使用的是客户端凭据流(AcquireTokenForClient),仅涉及应用级别的令牌,不需要初始化用户令牌缓存(UserTokenCache)。直接删除以下代码行即可:
_cacheProvider.Initialize(app.UserTokenCache);
2. 使用官方正式版分布式缓存集成(无需ExperimentalFeatures)
MSAL从v4版本开始已提供正式的分布式令牌缓存支持,无需依赖WithExperimentalFeatures。推荐改用Microsoft.Identity.Web包的集成方案:
- 首先安装NuGet包:
Microsoft.Identity.Web.TokenCache - 服务配置代码修改为:
services.AddMsal(options => { options.ClientId = "myClientId"; options.ClientSecret = "myClientSecret"; options.Authority = "https://mykeycloakinstance/auth/realms/master/"; }) .AddDistributedTokenCaches() .AddStackExchangeRedisCache(options => { options.Configuration = redisSettings.ConnectionString; options.InstanceName = redisSettings.InstanceName; });
- 在业务服务中直接注入
IConfidentialClientApplication来获取令牌:
private readonly IConfidentialClientApplication _msalApp; public TokenService(IConfidentialClientApplication msalApp) { _msalApp = msalApp; } public async Task<string> GetAccessTokenAsync(IEnumerable<string> scopes) { var result = await _msalApp.AcquireTokenForClient(scopes).ExecuteAsync(); return result.AccessToken; }
3. 自定义分区缓存实现(若需手动构建应用)
如果必须手动构建ConfidentialClientApplication,需确保应用令牌缓存和用户令牌缓存使用独立的缓存键分区,避免键冲突。自定义IMsalTokenCacheProvider时为两种缓存设置不同的键前缀:
public class PartitionedRedisTokenCacheProvider : IMsalTokenCacheProvider { private readonly IDistributedCache _distributedCache; private const string AppCachePrefix = "MSAL:AppCache:"; private const string UserCachePrefix = "MSAL:UserCache:"; public PartitionedRedisTokenCacheProvider(IDistributedCache distributedCache) { _distributedCache = distributedCache; } public async Task InitializeAsync(ITokenCache tokenCache, TokenCacheType cacheType) { var cacheKeyPrefix = cacheType == TokenCacheType.AppTokenCache ? AppCachePrefix : UserCachePrefix; // 实现基于前缀的序列化/反序列化逻辑 tokenCache.SetBeforeAccessAsync(async args => { var cacheKey = $"{cacheKeyPrefix}{args.ClientId}"; var cacheData = await _distributedCache.GetAsync(cacheKey); if (cacheData != null) { args.TokenCache.DeserializeMsalV3(cacheData); } }); tokenCache.SetAfterAccessAsync(async args => { if (args.HasStateChanged) { var cacheKey = $"{cacheKeyPrefix}{args.ClientId}"; await _distributedCache.SetAsync(cacheKey, args.TokenCache.SerializeMsalV3()); } }); } }
内容的提问来源于stack exchange,提问作者Robert
相关产品推荐
相关产品推荐

