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

MSAL分布式令牌缓存键冲突咨询:B2B访客跨租户令牌被覆盖

MSAL分布式令牌缓存(Redis)多租户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流程的令牌检索需求。

核心思路

缓存键需要同时包含两个维度的信息:

  1. 用户的原生唯一标识(保证用户身份的唯一性)
  2. 当前访问的租户ID(保证不同租户的缓存隔离)

实现步骤

  1. 继承MsalDistributedTokenCacheAdapter类,重写GetCacheKey()方法
  2. 从TokenCacheNotificationArgs中提取两个关键信息:
    • 用户原生租户标识:HomeAccountId.Identifier(即uid.utid格式的字符串)
    • 当前访问的租户ID:从令牌的tid声明或认证结果中获取
  3. 将两个信息拼接成唯一的缓存键,示例格式:{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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 00:42:29