Как настроить ASP.Net TestHost для работы с OpenId Connect?

У меня есть приложение ASP.Net Core, настроенное для выдачи и аутентификации токенов-носителей JWT. Клиенты могут успешно извлекать токены на предъявителя и аутентифицироваться с токеном, когда сайт размещен в Kestrel.

У меня также есть набор интеграционных тестов, которые используют Microsoft.AspNetCore.TestHost.TestServer. До добавления аутентификации тесты могли успешно отправлять запросы к приложению. После добавления аутентификации я начал получать ошибки, связанные с доступом к конфигурации открытого идентификатора. Конкретное исключение, которое я вижу, это:

info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 GET http://  
fail: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerMiddleware[3]
      Exception occurred while processing message.
System.InvalidOperationException: IDX10803: Unable to obtain configuration from: 'http://localhost/.well-known/openid-configuration'. ---> System.IO.IOException: IDX10804: Unable to retrieve document from: 'http://localhost/.well-known/openid-configuration'. ---> System.Net.Http.HttpRequestException: Response status code does not indicate success: 404 (Not Found).

Основываясь на моих исследованиях, это иногда срабатывает, когда для Authority задан хост, отличный от хост-сервера. Например, Kestrel работает по адресу http://localhost:5000/ по умолчанию, и это то, что я изначально установил для Authority, но после установки того, что эмулирует TestServer ( http://localhost/), он по-прежнему выдает ту же ошибку, Вот моя конфигурация аутентификации:

    app.UseJwtBearerAuthentication(new JwtBearerOptions
    {
        AutomaticAuthenticate = true,
        AutomaticChallenge = true,
        RequireHttpsMetadata = false,
        TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = signingKey,
            ValidateAudience = true
        },
        Audience = "Anything",
        Authority = "http://localhost"
    });

Странно то, что попытка получить URL-адрес непосредственно из теста интеграции работает нормально:

Итак, как настроить инфраструктуру ASP.Net TestServer и OpenId Connect для совместной работы?

=== РЕДАКТИРОВАТЬ ====

Размышляя об этом немного, мне пришло в голову, что проблема заключается в том, что внутреннее устройство авторизации JWT пытается сделать запрос на http://localhost/ порт 80, но не пытается сделать запрос с использованием TestServer и поэтому ищу настоящий сервер. Так как его нет, он никогда не будет аутентифицирован. Похоже, что следующим шагом будет посмотреть, есть ли какой-нибудь способ отключить проверку полномочий или каким-либо образом расширить инфраструктуру, чтобы позволить ей использовать TestServer в качестве хоста.

1 ответ

Решение

Инфраструктура JWT действительно пытается сделать HTTP-запрос по умолчанию. Я смог заставить его работать, установив свойство JwtBearerOptions.ConfigurationManager для нового экземпляра OpenIdConnectionConfigurationRetriever(), который предоставил IDocumentResolver, предоставленный DI:

        ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
            authority + "/.well-known/openid-configuration",
            new OpenIdConnectConfigurationRetriever(),
            _documentRetriever),

В производственном коде я просто регистрирую значение по умолчанию в своем контейнере (Autofac):

builder.RegisterType<HttpDocumentRetriever>().As<IDocumentRetriever>();

Я уже использовал производный класс Setup для своих интеграционных тестов, который следует шаблону "Метод шаблона" для настройки контейнера, поэтому я смог переопределить экземпляр IDocumentRetriever с помощью класса, который возвращает результаты из экземпляра TestServer.

Я столкнулся с дополнительным препятствием, которое заключается в том, что клиент TestServer, казалось, зависал, когда был сделан запрос (тот, который был инициирован из JWT, вызывающим мой IDocumentRetriever), в то время как другой запрос уже был ожидающим (тот, который инициировал запрос для начала), поэтому Мне нужно было сделать запрос заранее и предоставить кэшированные результаты из моего IDocumentRetriever:

public class TestServerDocumentRetriever : IDocumentRetriever
{
    readonly IOpenIdConfigurationAccessor _openIdConfigurationAccessor;

    public TestServerDocumentRetriever(IOpenIdConfigurationAccessor openIdConfigurationAccessor)
    {
        _openIdConfigurationAccessor = openIdConfigurationAccessor;
    }

    public Task<string> GetDocumentAsync(string address, CancellationToken cancel)
    {
        return Task.FromResult(_openIdConfigurationAccessor.GetOpenIdConfiguration());
    }
}
Другие вопросы по тегам