Как настроить 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());
}
}