首页 > 解决方案 > 内存集成测试中的 Web API - 两个服务之间的身份验证

问题描述

我在这里搜索了很多关于我的问题的帖子,但没有找到与我想要实现的目标相关的任何内容。

我创建了一个用于处理用户身份验证的 Web API 微服务。该服务使用身份框架并发布不记名令牌。我有另一个使用控制器上的 [Authroize] 属性保护的 Web API。两种服务都通过实体框架与同一个数据库通信。

在设置此基础架构时,我必须克服的一个障碍是两种服务都需要在其 web.configs 中使用相同的机器密钥。当一切都部署好后,它工作得很好。

但是,我正在尝试使用 Microsoft Owin TestServer 为我的主要 API 编写一些集成测试。这允许我针对 API 的内存副本编写集成测试,该 API 还使用实体框架迁移使用数据库的内存副本。

我已经成功地为我的用户服务创建了集成测试。但是,我正在努力让集成测试为我的主要服务工作。我创建了一个解决方案,其中包含一个新的测试项目。我还链接了我的用户服务和主要服务。在集成测试的引导程序中,我使用每个服务 Startup.cs 在内存中创建每个服务的副本。

一切正常,直到我尝试点击使用 [Authorize] 属性保护的控制器。我已成功点击用户服务注册用户,激活该用户并获取不记名令牌。然后,我在请求中将其传递给 auth 标头中的主要服务。但是,我总是来自服务的未经授权的响应。

我怀疑这再次与机器密钥有关,并尝试将相同的机器密钥放入集成测试项目中的 App.config 中。这没有任何区别。

如果有人设法使这种情况起作用,我将非常感谢有关可能导致问题的任何建议。我没有粘贴任何代码,因为涉及很多,但很乐意粘贴任何细节。

以这种方式进行测试非常快速且非常强大。但是,似乎缺乏有关使其正常工作的信息。

编辑

这是一些要求的代码。内存中的两个 API 在 OneTimeSetUp 中创建,如下所示:

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

然后从 API 中获取 Authorized HttpClient,如下所示:

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

“token”是从用户服务中获取的。

然后使用客户端执行 GET,如下所示:

var result = await client.GetAsync(uri);

编辑 2

我还应该指出,我已经在用户服务中工作了经过身份验证的集成测试。问题只是当我尝试同时使用这两种服务时。例如,从用户服务获取令牌并使用该令牌对我的主要服务进行身份验证。内存实体框架数据库创建如下:

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

标签: entity-framework-6asp.net-web-api2integration-testingowinin-memory

解决方案


因此,经过几天的努力,我终于找到了解决方案。我做的第一件事是将集成测试移回主 Web API。退后一步并更多地思考这个问题后,我意识到通过创建一个单独的解决方案并在内存中启动我的用户服务和 Web API,我真正测试的是身份框架而不是 API 本身。

对于未经身份验证和经过身份验证的调用,我的用户服务中的集成测试工作得很好。在我看来,我应该能够对 Web API 做同样的事情,而不是依赖用户服务。

我认为我必须能够“绕过” [Authorize] 属性来测试 API。经过一些谷歌搜索后,我发现以下文章对于使解决方案发挥作用最有价值:

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. 令牌需要按如下方式生成。我还需要为用户 ID 添加声明,因为这是从控制器中的请求中获取的。

    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;
    }
    

我希望这篇文章可以帮助其他有类似问题的人,并感谢所有做出贡献的人。


推荐阅读