我正在尝试使用 Katana 项目提供的 OpenID Connect 身份验证中间件。
实现中存在一个错误,在这些情况下会导致死锁:
- 在请求具有线程关联性的主机中运行(例如 IIS)。
- 尚未检索 OpenID Connect 元数据文档或缓存副本已过期。
- 应用程序调用
SignOut
身份验证方法。 - 应用程序中发生的操作会导致写入响应流。
死锁的发生是由于身份验证中间件处理来自主机的回调的方式,表明正在发送标头。问题的根源在于这种方法:
private static void OnSendingHeaderCallback(object state)
{
AuthenticationHandler handler = (AuthenticationHandler)state;
handler.ApplyResponseAsync().Wait();
}
来自Microsoft.Owin.Security.Infrastructure.AuthenticationHandler
仅当返回已经完成时,对的调用Task.Wait()
才是安全的,而在 OpenID Connect 中间件的情况下它还没有完成。Task
中间件使用 的实例Microsoft.IdentityModel.Protocols.ConfigurationManager<T>
来管理其配置的缓存副本。这是一个异步实现,使用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();
}
}
我已经尝试添加.ConfigureAwait(false)
所有等待的操作,以将延续编组到线程池,而不是 ASP.NET 工作线程,但我在避免死锁方面没有任何成功。
我可以解决更深层次的问题吗?我不介意更换组件——我已经创建了自己的IConfiguratioManager<T>
. 是否有一个简单的修复方法可以用来防止死锁?