2015-07-06 1 views
5

Я пытаюсь использовать промежуточное программное обеспечение проверки подлинности OpenID Connect, предоставляемое проектом Katana.Решение взаимоблокировки OnSendingHeaders с Katana OpenID Connect Middleware

Там ошибка в реализации, которая вызывает тупиковую ситуацию в этих условиях:

  1. Запуск в хозяине, где запрос имеет резьбу сродство (например, IIS).
  2. Документ метаданных OpenID Connect не был извлечен или срок действия кэшированной копии истек.
  3. Приложение вызывает метод идентификации SignOut.
  4. В приложении происходит действие, которое вызывает запись в поток ответов.

Тупик происходит из-за того, как промежуточное средство аутентификации обрабатывает обратный вызов от хоста, сигнализируя о отправке заголовков. Корень проблемы в этом методе:

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>. Есть ли простое исправление, которое может быть применено для предотвращения тупика?

+0

Я не надеюсь, что это что-то изменит, но попробуйте 'GetAwaiter(). GetResult()' вместо 'Wait()'. –

+0

@PauloMorgado К сожалению, код «OnSendingHeaders» глубоко погружен в класс «AuthenticationHandler» и не может быть изменен. –

+0

Кроме того, попробуйте дождаться, когда 'Task.IsComplete' будет истинным. Используйте [SpinWait] (https://msdn.microsoft.com/library/system.threading.spinwait.aspx «Структура SpinWait»), чтобы ждать без переключения контекста. –

ответ

2

@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

+0

Я буду повторно проверять и принимать, если исправлено. –

+0

@ Трэджиан сделал для вас эту работу? –

+0

У меня была только возможность коротко протестировать. Он прошел мой первоначальный тестовый сценарий, но мне нужно сделать больше, чтобы проверить, что я больше не могу создать тупик. Я приму этот ответ, когда получаю возможность правильно проверить исправление. –

1

Я не могу прокомментировать принятый ответ, но даже при том, что конкретная NuGet проблема, кажется, сохраняются для меня:/

Я я обнаружил, что мне нужно изменить строки ConfigurationManager # GetConfigurationAsync:

await _refreshLock.WaitAsync(cancel); 

в

_refreshLock.Wait(cancel); 

и

_currentConfiguration = await _configRetriever.GetConfigurationAsync(_metadataAddress, _docRetriever, CancellationToken.None) 

в

_currentConfiguration = _configRetriever.GetConfigurationAsync(_metadataAddress, _docRetriever, CancellationToken.None).Result; 

Или же я поставил ConfigureAwait (ложь) на обоих вызовов и обернуть 'GetConfigurationAsync' в другой метод, который блокируется вызовом «.Result» и возвращает его в новую уже завершенную задачу.

Если я это сделаю то тупики на выходе из системы больше не происходит для меня более чем на 1 пользователя (предыдущее исправление на имя одного пользователя выходе из системы.)

Однако ясно, что это делает его метод «GetConfigurationAsync» решительно synchronous:/

+1

С вашими предлагаемыми изменениями кода я все еще зашел в тупик. То, что я сделал, было изменено на '_refreshLock.Wait (cancel);' и 'await _configRetriever.GetConfigurationAsync (...). ConfigureAwait (false);'. Это мешает мне зайти в тупик, но не уверен, что код правильный. –