MSAL分布式令牌缓存键冲突咨询:B2B访客跨租户令牌被覆盖
问题背景
我发现MSAL搭配Redis的分布式令牌缓存默认以HomeAccountId作为缓存键,其生成规则分两种场景:
- 本地用户:键由
oid(用户对象ID)和tid(租户ID)拼接而成,格式为[oid].[tid] - B2B访客用户:键由
uid(用户原生租户的oid)和utid(用户原生租户ID)拼接而成,格式为[uid].[utid]
在SaaS多租户场景下出现了严重问题:租户B的用户同时是租户A的访客,该用户在租户B登录时,缓存键为[B.oid].[B.tid];后续访问租户A并登录时,缓存键为[A.uid].[A.utid]——但A.uid实际等于B.oid,A.utid等于B.tid,导致两个键完全相同,第二次登录的令牌直接覆盖了第一次的缓存内容。当用户返回租户B时,会错误获取到租户A的令牌,引发认证故障。
此前我误以为所有场景下缓存键都是[oid].[tid],目前只能通过重写MsalDistributedTokenCacheAdapter来控制缓存键(这是微软推荐的自定义方式),同时需要确保后端长期进程的OBO流程能正确检索对应令牌。
问题根源
MSAL对本地用户和B2B访客的HomeAccountId生成逻辑存在差异:
- 本地用户直接使用当前租户的
oid+tid作为标识 - B2B访客则使用原生租户的
uid(即原生租户的oid)+utid(原生租户的tid)作为标识
这种设计导致同一用户在不同租户作为访客时,HomeAccountId始终指向其原生租户的标识,和在原生租户登录时的缓存键完全一致,最终造成不同租户的令牌缓存互相覆盖。
解决方案:自定义缓存键生成逻辑
重写MsalDistributedTokenCacheAdapter的键生成逻辑,确保同一用户在不同租户的缓存键唯一,同时兼容OBO流程的令牌检索需求。
核心思路
缓存键需要同时包含两个维度的信息:
- 用户的原生唯一标识(保证用户身份的唯一性)
- 当前访问的租户ID(保证不同租户的缓存隔离)
实现步骤
- 继承
MsalDistributedTokenCacheAdapter类,重写GetCacheKey()方法 - 从
TokenCacheNotificationArgs中提取两个关键信息:- 用户原生租户标识:
HomeAccountId.Identifier(即uid.utid格式的字符串) - 当前访问的租户ID:从令牌的
tid声明或认证结果中获取
- 用户原生租户标识:
- 将两个信息拼接成唯一的缓存键,示例格式:
{HomeAccountId}_{TenantId}
代码示例
public class CustomMsalDistributedTokenCacheAdapter : MsalDistributedTokenCacheAdapter { public CustomMsalDistributedTokenCacheAdapter( IDistributedCache distributedCache, IOptions<MsalDistributedTokenCacheAdapterOptions> options, ILogger<MsalDistributedTokenCacheAdapter> logger) : base(distributedCache, options, logger) { } protected override string GetCacheKey(TokenCacheNotificationArgs args) { var homeAccountId = args.HomeAccountId?.Identifier; var currentTenantId = args.Token?.Claims.FirstOrDefault(c => c.Type == "tid")?.Value ?? args.AuthenticationResult?.TenantId; if (string.IsNullOrEmpty(homeAccountId) || string.IsNullOrEmpty(currentTenantId)) { // 当关键信息缺失时,回退到默认键生成逻辑 return base.GetCacheKey(args); } // 生成带租户隔离的唯一缓存键 return $"{homeAccountId}_{currentTenantId}"; } }
注意事项
- 确保OBO流程中能正确传递当前租户ID,以便后端进程检索对应租户的令牌缓存
- 上线前清理Redis中已存在的冲突缓存键,避免新旧缓存逻辑并存导致混乱
- 覆盖多场景测试:用户在原生租户登录、作为访客登录其他租户、切换租户访问等,验证缓存隔离效果
补充说明
微软默认的HomeAccountId逻辑是为了让用户跨租户访问时复用原生身份标识,但在SaaS多租户场景下,这种设计会导致令牌缓存冲突,因此必须通过自定义键生成逻辑来实现租户间的缓存隔离。
内容的提问来源于stack exchange,提问作者Brett

