【问题标题】:Web API In Memory Integration Testing - Authentication between two services内存集成测试中的 Web API - 两个服务之间的身份验证
【发布时间】:2018-06-05 13:08:14
【问题描述】:

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

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

在设置此基础架构时,我必须克服的一个障碍是两种服务都需要在其 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 中获取授权的 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>());
}

【问题讨论】:

  • 放一些你试过的代码。
  • 我确实在经历同样的事情。我使用的是相同的设置,尽管我使用的是 JWT 并且只是得到 Invalid Grant token 错误响应。奇怪的是它可以在本地工作,但是当我在 CI 构建上运行集成测试时,它们会失败。你的也在本地工作吗?
  • 我的也不能在本地工作。一切似乎都指向机器密钥,因为直到我在两个配置中都放置了相同的密钥之前,我在部署到 IIS 时遇到了同样的问题。目前它与 Visual Studio 中的测试隔离,还没有推送到 CI ......听起来我可能也会从中获得更多乐趣!我会按要求发布一些示例代码。
  • @py7133 - 当我没有在请求中使用 grant_type = password 时,我遇到了无效的授权错误。不确定它是否与 JWT 相同。
  • 从您上面发送的内容来看,至少通过使用 JWT,您将使用 POST 请求而不是 GET 请求。如果你交换它会发生什么?

标签: entity-framework-6 asp.net-web-api2 integration-testing owin in-memory


【解决方案1】:

所以经过几天的努力,我终于找到了解决方案。我做的第一件事是将集成测试移回主 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;
    }
    

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-18
    • 2013-11-22
    • 2017-07-30
    • 2020-04-05
    • 1970-01-01
    相关资源
    最近更新 更多