当我的 API 需要授权令牌时,如何使用 Asp.Net Core 2.0 的 in-memory TestServer class 进行集成测试?
How can I use Asp.Net Core 2.0's in-memory TestServer class for Integration Tests when my API requires an Authorization Token?
我正在开发 ASP.NET Core 2.0 Web API,我想使用 ASP.NET Core 的 TestServer class 进行一些集成测试。我使用 xUnit 作为我的测试框架,所以我创建了一个 TestServerFixture class 来创建 in-memory TestServer 实例,然后使用 TestServer 的 .CreateClient() 创建 HTTPClient 实例。
我的网站 API 需要来自我的 Azure AD 的 OAuth2.0 访问令牌。我在 Startup.cs、ConfigureServices 方法中使用此代码进行设置:
// Add Azure AD OAUTH2.0 Authentication Services
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddAzureAdBearer(options => Configuration.Bind("AzureAd", options));
在我的控制器中,我在 class 上有 [Authorize] 属性。
因此,对于我的集成测试设置,我的 TestServerFixture 中有一个方法可以从 Azure AD 获取有效令牌,并将其添加到我的客户端请求 header 中,如下所示;
Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", await _testServerFixture.GetAccessToken());
当我调试我的集成测试时,我可以看到请求确实包含一个有效的访问令牌,但是当我 运行 我的集成测试时,我仍然从 API 收到 401 Unauthorized。
在进行一些挖掘之后,我发现了一些资源,这些资源讨论了与 TestServer 类似的问题,但与身份验证相关,而不是授权,正如我所经历的那样。以下是这些资源的链接;
http://geeklearning.io/how-to-deal-with-identity-when-testing-an-asp-net-core-application/
这些都是关于使用自定义中间件将 ClaimsPrincipal 分配给 context.user。由于这是基于身份验证而不是授权,我不确定我是否可以为我的访问令牌做类似的事情。
我知道在我的 API 中,我可以访问 HTTPContext.User 并提取 AppId 值,它是访问令牌的一部分,所以看起来身份验证和授权都使用Context.User.
因此,在我为此花时间构建自己的自定义中间件之前,我想看看是否有人已经解决了这个问题,或者是否知道可以满足我需要的 NuGet。
编辑 - 解决方案
我展示这个是为了防止其他人 运行 陷入这个问题。
我最终构建了 Zach Bartlett 在他的 blog 中展示的中间件,但进行了以下更改。
public class AuthenticatedTestRequestMiddleware
{
#region Class Variables
private const string TestingAccessTokenAuthentication = "TestingAccessTokenAuthentication";
private readonly RequestDelegate _next;
#endregion Class Variables
#region Constructor(s)
public AuthenticatedTestRequestMiddleware(RequestDelegate next)
{
_next = next;
}
#endregion Constructor(s)
public async Task Invoke(HttpContext context)
{
if (context.Request.Headers.Keys.Contains("X-Integration-Testing"))
{
if (context.Request.Headers.Keys.Contains("Authorization"))
{
var token = context.Request.Headers["Authorization"].First();
var claimsIdentity = new ClaimsIdentity(new List<Claim>
{
new Claim(ClaimTypes.Authentication, token)
}, TestingAccessTokenAuthentication);
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
context.User = claimsPrincipal;
}
}
await _next(context);
}
}
有一个很有趣 "Gotcha"。
Zach 的博客中有代码;
public const string TestingHeader = "X-Integration-Testing";
在他的中间件的顶部,然后在测试中引用 TestingHeader 以获取 header collection 中的密钥,就像这样;
if (context.Request.Headers.Keys.Contains(TestingHeader)
在我将字符串文字而不是变量放入 .Contains() 子句之前,这样做对我来说是失败的。
现在,我的集成测试通过了 200 OK 响应。 :)
我找到了遵循 Zach Bartlett 的 blog post 的解决方案,并进行了一些小的更改以使其适用于身份验证 header。代码显示为我上面的原始 post 中的编辑。
我正在开发 ASP.NET Core 2.0 Web API,我想使用 ASP.NET Core 的 TestServer class 进行一些集成测试。我使用 xUnit 作为我的测试框架,所以我创建了一个 TestServerFixture class 来创建 in-memory TestServer 实例,然后使用 TestServer 的 .CreateClient() 创建 HTTPClient 实例。
我的网站 API 需要来自我的 Azure AD 的 OAuth2.0 访问令牌。我在 Startup.cs、ConfigureServices 方法中使用此代码进行设置:
// Add Azure AD OAUTH2.0 Authentication Services
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddAzureAdBearer(options => Configuration.Bind("AzureAd", options));
在我的控制器中,我在 class 上有 [Authorize] 属性。
因此,对于我的集成测试设置,我的 TestServerFixture 中有一个方法可以从 Azure AD 获取有效令牌,并将其添加到我的客户端请求 header 中,如下所示;
Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", await _testServerFixture.GetAccessToken());
当我调试我的集成测试时,我可以看到请求确实包含一个有效的访问令牌,但是当我 运行 我的集成测试时,我仍然从 API 收到 401 Unauthorized。
在进行一些挖掘之后,我发现了一些资源,这些资源讨论了与 TestServer 类似的问题,但与身份验证相关,而不是授权,正如我所经历的那样。以下是这些资源的链接;
http://geeklearning.io/how-to-deal-with-identity-when-testing-an-asp-net-core-application/
这些都是关于使用自定义中间件将 ClaimsPrincipal 分配给 context.user。由于这是基于身份验证而不是授权,我不确定我是否可以为我的访问令牌做类似的事情。
我知道在我的 API 中,我可以访问 HTTPContext.User 并提取 AppId 值,它是访问令牌的一部分,所以看起来身份验证和授权都使用Context.User.
因此,在我为此花时间构建自己的自定义中间件之前,我想看看是否有人已经解决了这个问题,或者是否知道可以满足我需要的 NuGet。
编辑 - 解决方案
我展示这个是为了防止其他人 运行 陷入这个问题。
我最终构建了 Zach Bartlett 在他的 blog 中展示的中间件,但进行了以下更改。
public class AuthenticatedTestRequestMiddleware
{
#region Class Variables
private const string TestingAccessTokenAuthentication = "TestingAccessTokenAuthentication";
private readonly RequestDelegate _next;
#endregion Class Variables
#region Constructor(s)
public AuthenticatedTestRequestMiddleware(RequestDelegate next)
{
_next = next;
}
#endregion Constructor(s)
public async Task Invoke(HttpContext context)
{
if (context.Request.Headers.Keys.Contains("X-Integration-Testing"))
{
if (context.Request.Headers.Keys.Contains("Authorization"))
{
var token = context.Request.Headers["Authorization"].First();
var claimsIdentity = new ClaimsIdentity(new List<Claim>
{
new Claim(ClaimTypes.Authentication, token)
}, TestingAccessTokenAuthentication);
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
context.User = claimsPrincipal;
}
}
await _next(context);
}
}
有一个很有趣 "Gotcha"。
Zach 的博客中有代码;
public const string TestingHeader = "X-Integration-Testing";
在他的中间件的顶部,然后在测试中引用 TestingHeader 以获取 header collection 中的密钥,就像这样;
if (context.Request.Headers.Keys.Contains(TestingHeader)
在我将字符串文字而不是变量放入 .Contains() 子句之前,这样做对我来说是失败的。
现在,我的集成测试通过了 200 OK 响应。 :)
我找到了遵循 Zach Bartlett 的 blog post 的解决方案,并进行了一些小的更改以使其适用于身份验证 header。代码显示为我上面的原始 post 中的编辑。