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

Azure AD B2C自定义PolicyConfigurationManager的MergeConfig偶发空引用异常问题

问题根因
  1. Clone方法违规修改共享缓存对象
    你当前的Clone方法为了完成克隆,直接清空了传入的OpenIdConnectConfiguration实例的SigningKeys集合,而这个实例是内置ConfigurationManager缓存的全局共享对象。当并发请求刚好走到Clear操作和重新回填密钥的间隙,其他线程读取该配置的SigningKeys时就会拿到空/无效的密钥项,触发空引用。
  2. 缺少线程安全控制
    PolicyConfigurationManager是单例实例,OpenIdConnectConfiguration的SigningKeys等集合都是非线程安全的,多线程并发执行合并配置、元数据自动刷新操作时,会出现枚举集合过程中集合被修改、拿到null元素的问题。
  3. 空值校验缺失
    MergeConfig方法中判断密钥是否存在时,没有校验SecurityKey实例本身是否为null、KeyId是否为null,当出现无效密钥项时直接访问KeyId就会抛出空引用异常。
解决方案
  • 重构Clone方法,禁止修改原配置对象
    不要修改传入的原始配置实例,调整克隆逻辑不触碰原对象的成员:
private static OpenIdConnectConfiguration Clone(OpenIdConnectConfiguration configuration)
{
    // 直接序列化完整对象,不需要清空原对象的成员
    var json = OpenIdConnectConfiguration.Write(configuration);
    var clone = OpenIdConnectConfiguration.Create(json);
    
    // 手动同步签名密钥,避免序列化丢失非公开属性
    clone.SigningKeys.Clear();
    foreach (var key in configuration.SigningKeys)
    {
        clone.SigningKeys.Add(key);
    }
    clone.JsonWebKeySet = configuration.JsonWebKeySet;
    return clone;
}
  • 增加空值校验,修复MergeConfig的空引用风险
    修改MergeConfig中密钥判断的逻辑,提前做空值过滤:
private static void MergeConfig(OpenIdConnectConfiguration result, OpenIdConnectConfiguration source)
{
    // 其他集合遍历也建议先判断是否为null,避免元数据返回异常
    if (source.IdTokenSigningAlgValuesSupported != null)
    {
        foreach (var alg in source.IdTokenSigningAlgValuesSupported)
        {
            if (!result.IdTokenSigningAlgValuesSupported.Contains(alg))
            {
                result.IdTokenSigningAlgValuesSupported.Add(alg);
            }
        }
    }

    if (source.ResponseTypesSupported != null)
    {
        foreach (var type in source.ResponseTypesSupported)
        {
            if (!result.ResponseTypesSupported.Contains(type))
            {
                result.ResponseTypesSupported.Add(type);
            }
        }
    }

    if (source.SubjectTypesSupported != null)
    {
        foreach (var type in source.SubjectTypesSupported)
        {
            if (!result.SubjectTypesSupported.Contains(type))
            {
                result.SubjectTypesSupported.Add(type);
            }
        }
    }

    if (source.SigningKeys != null)
    {
        foreach (var key in source.SigningKeys.Where(k => k != null && !string.IsNullOrEmpty(k.KeyId)))
        {
            if (result.SigningKeys.All(k => k != null && !string.IsNullOrEmpty(k.KeyId) && k.KeyId != key.KeyId))
            {
                result.SigningKeys.Add(key);
            }
        }
    }
}
  • 增加线程安全锁
    在PolicyConfigurationManager中添加全局锁,所有涉及配置读取、合并、刷新的操作都加锁,避免并发冲突:
private readonly object _syncLock = new object();

public async Task<OpenIdConnectConfiguration> GetConfigurationAsync(CancellationToken cancel)
{
    OpenIdConnectConfiguration mergedConfiguration = null;
    foreach (var manager in managers)
    {
        var configuration = await manager.Value.GetConfigurationAsync(cancel);
        lock (_syncLock) // 加锁保证合并、克隆操作原子性
        {
            if (mergedConfiguration == null)
                mergedConfiguration = Clone(configuration);
            else
                MergeConfig(mergedConfiguration, configuration);
        }
    }
    return mergedConfiguration;
}

public void RequestRefresh()
{
    lock (_syncLock)
    {
        foreach (var manager in managers)
        {
            manager.Value.RequestRefresh();
        }
    }
}
  • 可选优化:调整元数据刷新间隔
    如果不需要太频繁的元数据刷新,可以在构造内置ConfigurationManager的时候指定刷新间隔,降低刷新和业务请求冲突的概率:
public PolicyConfigurationManager(string authority, IEnumerable<string> policies)
{
    foreach (var policy in policies)
    {
        var metadataAddress = $"{authority}/.well-known/openid-configuration?{PolicyParameter}={policy}";
        managers.Add(policy.ToLowerInvariant(), 
            new ConfigurationManager<OpenIdConnectConfiguration>(
                metadataAddress, 
                new OpenIdConnectConfigurationRetriever(),
                new HttpDocumentRetriever(),
                TimeSpan.FromDays(7) // 调整为7天刷新一次,按需修改
            ));
    }
}

内容的提问来源于stack exchange,提问作者Flying Squirrel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 17:45:01