ASP.NET Core 2.0 TestServer集成测试带Azure AD令牌遇401问题
我最近在开发ASP.NET Core 2.0 Web API时,用xUnit结合TestServer做集成测试——专门写了TestServerFixture类来生成内存TestServer实例,顺便创建对应的HttpClient。API本身要求携带Azure AD的OAuth2.0访问令牌才能访问,所以在Startup.cs的ConfigureServices里配置了AddAzureAdBearer认证服务,控制器也都加了[Authorize]特性。
本来以为测试流程很顺:从Azure AD拿到有效令牌,加到请求头里调用API就行,结果每次都返回401未授权,折腾了好一阵。后来参考思路实现自定义中间件并做了修改,终于解决了问题,还顺带发现了一个关于请求头判断的坑。
问题根源与解决思路
在用TestServer做内存集成测试时,Azure AD的Bearer认证中间件在验证令牌的流程里,对内存环境的适配有问题——哪怕令牌是有效的,也没法正确完成认证流程。这时候我们可以通过自定义中间件,在测试环境下绕过或者调整认证逻辑,让测试请求能正常通过授权。
1. 自定义测试认证中间件实现
我写了一个专门用于测试环境的中间件,手动处理用户身份的设置,代码如下:
public class TestAuthMiddleware { private readonly RequestDelegate _next; public TestAuthMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { // 重点:别用硬编码的"Authorization"字符串判断请求头! // 之前踩坑就是这里——用字面量时,有时候会因为客户端传的头大小写不一致(比如小写的authorization)导致匹配失败 if (context.Request.Headers.TryGetValue(HeaderNames.Authorization, out var authHeader)) { // 模拟认证通过,手动构造用户身份 var testClaims = new List<Claim> { new Claim(ClaimTypes.NameIdentifier, "test-user-id"), new Claim(ClaimTypes.Name, "TestUser"), // 根据API的权限要求,添加对应的Role或Scope Claim new Claim(ClaimTypes.Role, "Admin") }; var identity = new ClaimsIdentity(testClaims, "TestAuthentication"); context.User = new ClaimsPrincipal(identity); } await _next(context); } } // 扩展方法方便注册中间件 public static class TestAuthMiddlewareExtensions { public static IApplicationBuilder UseTestAuthentication(this IApplicationBuilder app) { return app.UseMiddleware<TestAuthMiddleware>(); } }
2. 仅在测试环境启用该中间件
在Startup.cs的Configure方法里,判断当前环境是否为测试环境,只在测试时用自定义中间件,生产环境还是走正常的Azure AD认证:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsEnvironment("Testing")) { // 测试环境用自定义认证中间件 app.UseTestAuthentication(); } else { // 生产/开发环境用Azure AD认证 app.UseAuthentication(); } // 其他中间件配置(比如UseMvc等)... }
3. 踩过的大坑:字符串字面量判断请求头
一开始我用context.Request.Headers.ContainsKey("Authorization")来判断请求头,结果有时候明明请求里带了令牌,却匹配不到。后来才发现,HTTP请求头的名称是大小写不敏感的,但用字符串字面量硬编码时,可能和客户端实际传递的头名称大小写不一致(比如有些客户端会传小写的authorization),导致判断失败。换成HeaderNames.Authorization这个官方常量就没问题了,它会自动处理大小写匹配的问题。
内容的提问来源于stack exchange,提问作者whiskytangofoxtrot

