Веб-API в тестировании интеграции с памятью — аутентификация между двумя службами

Я искал здесь много сообщений о моей проблеме, но не нашел ничего, связанного с тем, чего я пытаюсь достичь.

Я создал микрослужбу веб-API для обработки аутентификации пользователей. Служба использует структуру идентификации и выпускает токены носителя. У меня есть еще один веб-API, который защищен с помощью атрибута [Autroize] на контроллерах. Обе службы взаимодействуют с одной и той же базой данных через Entity Framework.

Одно препятствие, которое мне пришлось преодолеть при настройке этой инфраструктуры, заключалось в том, что обе службы требуют одного и того же машинного ключа в своих web.configs. Когда все развернуто, все работает отлично.

Однако я пытаюсь написать несколько интеграционных тестов для своего основного API, используя Microsoft Owin TestServer. Это позволяет мне писать интеграционные тесты для копии API в памяти, которая также использует копию базы данных в памяти с помощью миграций инфраструктуры сущностей.

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

Все работает нормально, пока я не попытаюсь нажать на контроллер, защищенный атрибутом [Authorize]. Я успешно попал в службу пользователя, чтобы зарегистрировать пользователя, активировать этого пользователя и получить токен носителя. Затем я передаю это в запросе основной службе в заголовке аутентификации. Однако я всегда получаю Несанкционированный ответ от службы.

Я подозреваю, что это снова связано с машинными ключами, и я попытался поместить тот же машинный ключ в App.config в рамках проекта интеграционного тестирования. Это не имело никакого значения.

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

Тестирование таким образом очень быстрое и действительно мощное. Однако, похоже, не хватает информации о том, как заставить это работать.

ИЗМЕНИТЬ

Вот некоторый код по запросу. Два API в памяти создаются в OneTimeSetUp следующим образом:

_userApi = TestServer.Create<UserApi.Startup>();
_webApi = TestServer.Create<WebApi.Startup>();

Авторизованный HttpClient затем берется из API следующим образом:

var client = _webApi.HttpClient;
var authorizationValue = new AuthenticationHeaderValue("Bearer", token);
client.DefaultRequestHeaders.Authorization = authorizationValue;

«токен» получен от службы пользователя.

Затем GET выполняется с использованием клиента следующим образом:

var result = await client.GetAsync(uri);

ИЗМЕНИТЬ 2

Я также должен отметить, что у меня есть аутентифицированные интеграционные тесты, работающие в User Service. Проблема только тогда, когда я пытаюсь использовать оба сервиса вместе. Например. Получите токен из пользовательской службы и используйте этот токен для аутентификации в моей основной службе. База данных инфраструктуры сущностей в памяти создается следующим образом:

AppDomain.CurrentDomain.SetData("DataDirectory", Path.Combine(TestContext.CurrentContext.TestDirectory, string.Empty));
using (new ContractManagerDatabase())
{
System.Data.Entity.Database.SetInitializer(new DropCreateDatabaseAlways<ContractManagerDatabase>());
}

person DClayton    schedule 05.06.2018    source источник
comment
Поместите код, который вы пробовали.   -  person onetwo12    schedule 05.06.2018
comment
Я буквально переживаю то же самое. Я использую ту же настройку, хотя я использую JWT и просто получаю ответы об ошибках токена Invalid Grant. Странно то, что он работает локально, но когда я запускаю интеграционные тесты на сборках CI, они терпят неудачу. Ваши тоже работают локально?   -  person py7133    schedule 05.06.2018
comment
Мои тоже локально не работают. Кажется, все указывает на ключ машины, поскольку, пока я не поместил один и тот же ключ в обе конфигурации, у меня была такая же проблема при развертывании в IIS. На данный момент он изолирован от тестов в Visual Studio, еще не перешел на CI ...... звучит так, как будто я тоже могу получить больше удовольствия от этого! Я опубликую пример кода по запросу.   -  person DClayton    schedule 05.06.2018
comment
@ py7133 - у меня были недопустимые ошибки предоставления, когда я не использовал в запросе grant_type = password. Не уверен, что то же самое с JWT.   -  person DClayton    schedule 05.06.2018
comment
Только из того, что вы отправили выше, по крайней мере, с помощью JWT, вы будете использовать запрос POST, а не запрос GET. Что произойдет, если вы поменяете это?   -  person py7133    schedule 05.06.2018
comment
К сожалению нет. Мой вызов пользовательской службы для получения токена — это запрос POST. Затем я пытаюсь протестировать метод HttpGet на контроллере, защищенном атрибутом Authorize.   -  person DClayton    schedule 05.06.2018
comment
Да, это звучит правильно. Я неправильно прочитал. На самом деле я получил свои сборки, чтобы начать работать. Используются ли другие секретные/подписывающие ключи, которые могут быть одинаковыми на двух серверах?   -  person py7133    schedule 05.06.2018
comment
Единственный MachineKey, о котором я знаю. Как ты заставил свою работать? У вас есть другие ключи?   -  person DClayton    schedule 05.06.2018
comment
К сожалению, моя проблема была немного проще, чем ваша, поскольку я неправильно понял часть о том, что у вас есть два API, которым необходимо взаимодействовать друг с другом. У меня есть только один, и когда я использовал JWT, у меня был секрет + ключ подписи, настроенный с помощью OAuthAuthorizationServerOptions и JwtBearerAuthenticationOptions — они работали без проблем. Вы смотрели на это? bitoftech .net/2014/09/24/   -  person py7133    schedule 05.06.2018
comment
Спасибо за ссылку, я использовал ее при первоначальной настройке архитектуры. Однако до интеграционного тестирования с in-memory API дело не доходит.   -  person DClayton    schedule 05.06.2018
comment
Проверяли ли вы с помощью in-memory API, что оба тестовых сервера OWIN указывают на один и тот же контекст базы данных? Интересно, если ваши токены уже принимаются в основном API и просто не находят пользователя из контекста БД, с которым был создан основной API, это, вероятно, вернет неавторизованный. Когда я решил свою проблему, мне пришлось обновить код, чтобы убедиться, что ApplicationOAuthProviders создавали объекты контекста для ApplicationUserManager, ApplicationRoleManager и контекста БД с помощью версии контекста БД в памяти вместо исходного контекста БД.   -  person py7133    schedule 05.06.2018
comment
Сначала я подумал, что это может быть раскручивание двух баз данных в памяти; по одному на каждую услугу. Однако я могу удалить атрибут авторизации и запросить пользователей в базе данных, что показывает, что зарегистрированный пользователь присутствует. Однако я проверю дальше в ApplicationUserManager!   -  person DClayton    schedule 05.06.2018


Ответы (1)


Итак, после нескольких дней борьбы с этим я наконец нашел решение. Первое, что я сделал, — переместил интеграционные тесты обратно в основной веб-API. Сделав шаг назад и подумав больше о проблеме, я понял, что, создав отдельное решение и развернув в памяти свой пользовательский сервис и веб-API, все, что я действительно тестировал, — это Identity Framework, а не сам API.

У меня есть интеграционные тесты, которые отлично работают в моей пользовательской службе как для неаутентифицированных, так и для аутентифицированных вызовов. Мне казалось, что я должен уметь делать то же самое с веб-API и не полагаться на пользовательский сервис.

Я подумал, что должен иметь возможность «обойти» атрибут [Authorize] для тестирования API. После некоторого поиска в Google я нашел следующую статью, которая была наиболее ценной для того, чтобы решение заработало:

https://blogs.taiga.nl/martijn/2016/03/10/asp-net-web-api-owin-authenticated-integration-tests-without-authorization-server/

Ключевыми для меня были следующие вещи:

  1. HTTP-клиент должен был быть построен по-другому для теста.

    var client = new HttpClient(_webApp.Handler) {BaseAddress = new Uri("http://localhost")};
    
  2. Вызов GET должен был быть построен по-другому для теста.

    token = token == string.Empty ? GenerateToken(userName, userId) : token;
    var request = new HttpRequestMessage(HttpMethod.Get, uri);
    request.Headers.Add("Authorization", "Bearer " + token);
    var client = GetAuthenticatedClient(token);
    return await client.SendAsync(request);
    
  3. Токен должен быть сгенерирован следующим образом. Мне также нужно было добавить требование для идентификатора пользователя, поскольку оно берется из запроса в контроллере.

    private static string GenerateToken(string userName, int userId)
    {
        var claims = new[]
        {
            new Claim(ClaimTypes.Name, userName),
            new Claim(ClaimTypes.NameIdentifier, userId.ToString()) 
        };
        var identity = new ClaimsIdentity(claims, "Test");
    
        var properties = new AuthenticationProperties { ExpiresUtc = DateTime.UtcNow.AddHours(1) };
        var ticket = new AuthenticationTicket(identity, properties);
        var format = new TicketDataFormat(_dataProtector);
        var token = format.Protect(ticket);
        return token;
    }
    

Я надеюсь, что этот пост поможет другим с похожими проблемами, и спасибо всем, кто внес свой вклад.

person DClayton    schedule 08.06.2018