Web API 内存中集成测试 - 两个服务之间的身份验证

Web API In Memory Integration Testing - Authentication between two services

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

我创建了一个 Web API 微服务来处理用户身份验证。该服务使用 identity framework 并发布不记名令牌。我有另一个 Web API,它使用控制器上的 [Authroize] 属性进行保护。两种服务都通过 Entity Framework.

与同一个数据库通信

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

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

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

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

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

如果有人设法让这个场景正常工作,我将非常感谢任何关于可能导致问题的建议。我没有粘贴任何代码,因为涉及的内容很多,但很乐意粘贴任何细节。

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

编辑

这里是一些请求的代码。内存中的两个APIs是在一个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

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

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

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

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

我认为我必须能够"bypass" [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. 令牌需要生成如下。我还需要为用户 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;
    }
    

我希望这 post 能帮助其他遇到类似问题的人,并感谢所有做出贡献的人。