You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 06:33:17