Я пытаюсь использовать промежуточное программное обеспечение проверки подлинности OpenID Connect, предоставляемое проектом Katana.Решение взаимоблокировки OnSendingHeaders с Katana OpenID Connect Middleware
Там ошибка в реализации, которая вызывает тупиковую ситуацию в этих условиях:
- Запуск в хозяине, где запрос имеет резьбу сродство (например, IIS).
- Документ метаданных OpenID Connect не был извлечен или срок действия кэшированной копии истек.
- Приложение вызывает метод идентификации
SignOut
. - В приложении происходит действие, которое вызывает запись в поток ответов.
Тупик происходит из-за того, как промежуточное средство аутентификации обрабатывает обратный вызов от хоста, сигнализируя о отправке заголовков. Корень проблемы в этом методе:
private static void OnSendingHeaderCallback(object state)
{
AuthenticationHandler handler = (AuthenticationHandler)state;
handler.ApplyResponseAsync().Wait();
}
От Microsoft.Owin.Security.Infrastructure.AuthenticationHandler
вызов Task.Wait()
является единственным безопасным, когда вернулся Task
уже завершена, что он не сделал в случае OpenID Connect промежуточное программное обеспечение.
Среднее программное обеспечение использует экземпляр 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>
. Есть ли простое исправление, которое может быть применено для предотвращения тупика?
Я не надеюсь, что это что-то изменит, но попробуйте 'GetAwaiter(). GetResult()' вместо 'Wait()'. –
@PauloMorgado К сожалению, код «OnSendingHeaders» глубоко погружен в класс «AuthenticationHandler» и не может быть изменен. –
Кроме того, попробуйте дождаться, когда 'Task.IsComplete' будет истинным. Используйте [SpinWait] (https://msdn.microsoft.com/library/system.threading.spinwait.aspx «Структура SpinWait»), чтобы ждать без переключения контекста. –