使用Katana OpenID Connect Middleware解决OnSendingHeaders死锁问题

Pau*_*ner 5 c# system.web task-parallel-library async-await owin

我正在尝试使用Katana项目提供的OpenID Connect身份验证中间件.

实现中存在一个错误,在这些情况下会导致死锁:

  1. 在请求具有线程关联性的主机(例如IIS)中运行.
  2. 尚未检索到OpenID Connect元数据文档或缓存副本已过期.
  3. 应用程序需要SignOut身份验证方法.
  4. 应用程序中发生的操作会导致写入响应流.

由于身份验证中间件处理来自主机发送标头的回调信号,因此发生死锁.问题的根源在于此方法:

private static void OnSendingHeaderCallback(object state)
{
    AuthenticationHandler handler = (AuthenticationHandler)state;
    handler.ApplyResponseAsync().Wait();
}
Run Code Online (Sandbox Code Playgroud)

来自Microsoft.Owin.Security.Infrastructure.AuthenticationHandler

Task.Wait()只有在返回Task已完成时,调用才是安全的,而在OpenID Connect中间件的情况下,调用尚未完成.

中间件使用一个实例Microsoft.IdentityModel.Protocols.ConfigurationManager<T>来管理其配置的缓存副本.这是一个asychnronous实现,使用SemaphoreSlim异步锁和HTTP文档检索器来获取配置.我怀疑这是死锁Wait()调用的触发器.

这是我怀疑的原因:

public async Task<T> GetConfigurationAsync(CancellationToken cancel)
{
    DateTimeOffset now = DateTimeOffset.UtcNow;
    if (_currentConfiguration != null && _syncAfter > now)
    {
        return _currentConfiguration;
    }

    await _refreshLock.WaitAsync(cancel);
    try
    {
        Exception retrieveEx = null;
        if (_syncAfter <= now)
        {
            try
            {
                // Don't use the individual CT here, this is a shared operation that shouldn't be affected by an individual's cancellation.
                // The transport should have it's own timeouts, etc..

                _currentConfiguration = await _configRetriever.GetConfigurationAsync(_metadataAddress, _docRetriever, CancellationToken.None);
                Contract.Assert(_currentConfiguration != null);
                _lastRefresh = now;
                _syncAfter = DateTimeUtil.Add(now.UtcDateTime, _automaticRefreshInterval);
            }
            catch (Exception ex)
            {
                retrieveEx = ex;
                _syncAfter = DateTimeUtil.Add(now.UtcDateTime, _automaticRefreshInterval < _refreshInterval ? _automaticRefreshInterval : _refreshInterval);
            }
        }

        if (_currentConfiguration == null)
        {
            throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10803, _metadataAddress ?? "null"), retrieveEx);
        }

        // Stale metadata is better than no metadata
        return _currentConfiguration;
    }
    finally
    {
        _refreshLock.Release();
    }
}
Run Code Online (Sandbox Code Playgroud)

我已经尝试添加.ConfigureAwait(false)所有等待的操作,以便将继续编组到线程池,而不是ASP.NET工作线程,但我没有成功避免死锁.

我能解决一个更深层次的问题吗?我不介意更换组件 - 我已经创建了自己的实验实现IConfiguratioManager<T>.是否有一个可以应用于防止死锁的简单修复?

Bre*_*ltz 3

@Tragedian 我们针对此问题采取了这些修复措施。您能否更新并查看问题是否仍然存在(我们以为我们已经用 184 修复了该问题,但正如您所见,我们修复了 185)。另一位客户在最新的 nuget 上取得了成功。

http://www.nuget.org/packages/Microsoft.IdentityModel.Protocol.Extensions/1.0.2.206221351

https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/185/files

https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/184/files