我正在尝试使用授权的端点为我的ASP.NET Web API设置集成测试。
我遵循了Microsoft的文档,将模拟身份验证添加到集成测试中,以允许测试客户端访问授权的端点。https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-6.0
例如
builder.ConfigureTestServices(services =>
{
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes("Test")
.RequireAuthenticatedUser()
.Build();
});
}
如果您使用的是默认身份验证方案,则可以在集成测试启动时更改该方案以使用测试方案。但是,我的授权端点使用指定的AuthenticationSchemes
,因此测试方案永远不会被授权用于端点。例如
[Authorize(AuthenticationSchemes = "Scheme1,Scheme2")]
public class AppVersionController : ControllerBase
{
...
}
我可以通过在测试时指定一个环境变量、检查该变量并将测试方案动态添加到授权端点来解决这个问题。然而,这为应用程序添加了许多特定于测试的逻辑,这在主项目中并不好。
这将起作用:
// Test scheme added dynamically from an environment variable to get the below result
[Authorize(AuthenticationSchemes = "Scheme1,Scheme2,Test")]
public class AppVersionController : ControllerBase
{
...
}
我通过创建一个基本上看起来像这样的自定义属性来完成这项工作:
public class AuthorizeAll : AuthorizeAttribute
{
public AuthorizeAll()
{
var authenticationSchemes = "Scheme1,Scheme2";
if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Testing")
{
authenticationSchemes += ",Test";
}
AuthenticationSchemes = authenticationSchemes;
}
}
我只是不喜欢我们将如何在应用层中继续维护这种测试身份验证方案,以及这种方法的安全问题。
问题
当设置了特定的身份验证方案时,为.NET集成测试授权端点的最佳方式是什么?
在单元测试运行测试所需的特定逻辑时,检查应用程序中的环境变量是否是一种好的做法?
目前使用的主要身份验证方案是使用JWT,那么有没有更好的方法来模拟测试中的JWT呢?
有很多方法可以模拟身份验证(由授权策略触发(。根据你的应用程序设置,你可以选择什么是";最好的";为你。
1.使用默认策略
在操作/控制器上使用[Authorize(AuthenticationSchemes = "Scheme1,Scheme2")]
时,在评估之前会组合默认授权策略。在测试设置中,您可以将带有test方案的空策略与实际代码中的默认策略相结合,以确保test计划始终运行。
builder.ConfigureTestServices(services =>
{
services.AddAuthorization(opt =>
{
opt.DefaultPolicy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes("Test")
.Combine(opt.DefaultPolicy)
.Build();
});
});
上面的代码将使用三个策略对请求进行身份验证:Scheme1、Scheme2和Test。如果您也有[Authorize(Policy = "Policy1")]
,这将不适用于您。
2.使用自定义策略评估器
ASP.NET Core评估类型为IPolicyEvaluator
的服务中的授权策略。您可以在测试中覆盖此服务,以便始终使用特定方案对请求进行身份验证。您也可以根据请求执行此操作。
class TestPolicyEvaluator : IPolicyEvaluator
{
private readonly PolicyEvaluator _innerEvaluator;
public TestPolicyEvaluator(PolicyEvaluator innerEvaluator)
{
_innerEvaluator = innerEvaluator;
}
public Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy,
HttpContext context)
{
var combinedPolicy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes("Test")
.Combine(policy)
.Build();
return _innerEvaluator.AuthenticateAsync(combinedPolicy, context);
}
public Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy,
AuthenticateResult authenticationResult, HttpContext context, object? resource)
{
return _innerEvaluator.AuthorizeAsync(policy, authenticationResult,
context, resource);
}
}
然后将其添加到您的测试设置中:
builder.ConfigureTestServices(services =>
{
services.AddTransient<IPolicyEvaluator>(serviceProvider => new TestPolicyEvaluator(
ActivatorUtilities.CreateInstance<PolicyEvaluator>(serviceProvider)));
});
这与默认策略解决方案类似,应该适用于所有策略评估场景。
3.使用自定义安全令牌验证
您还可以自定义JWT令牌的验证方式。您可以调整令牌验证参数,以允许测试特定的JWT令牌。或者,您也可以使用自定义的令牌验证器。
class TestJwtSecurityTokenHandler : ISecurityTokenValidator
{
private readonly JwtSecurityTokenHandler _innerHandler = new();
public bool CanValidateToken => _innerHandler.CanValidateToken;
public int MaximumTokenSizeInBytes
{
get => _innerHandler.MaximumTokenSizeInBytes;
set => _innerHandler.MaximumTokenSizeInBytes = value;
}
public bool CanReadToken(string securityToken) => true;
public ClaimsPrincipal ValidateToken(string securityToken,
TokenValidationParameters validationParameters,
out SecurityToken validatedToken)
{
validatedToken = new JwtSecurityToken();
return new ClaimsPrincipal(new ClaimsIdentity("Test"));
}
}
假设您有一个名为Scheme1的JWT方案,您可以在测试设置中配置它:
builder.ConfigureTestServices(services =>
{
services.Configure<JwtBearerOptions>("Scheme1", opt =>
{
// Tweak parameters
opt.TokenValidationParameters.ValidateLifetime = false;
// Or, fully override token validator
opt.SecurityTokenValidators.Clear();
opt.SecurityTokenValidators.Add(new TestJwtSecurityTokenHandler());
});
});
回答您的问题:
- 在测试中使用auth标头丰富
HttpClient
。使用它 - 否
- 看看(1(
Microsoft对如何对ASP.NET Core项目进行集成测试有很好的解释。通常,您在[OneTimeSetUp]
上启动服务,在[OneTimeTearDown]
上关闭服务,在测试中,您可以使用简单的HttpClient
与服务交互,与客户端交互的方式相同。
例如:
[Fact]
public async Task HelloWorldTest()
{
//your MyApp.Web.Program
var application = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder =>
{
// here you can mock some external dependencies of your service, such as logging or database
});
//your http client to connect, you can tweak headers here
using var client = application.CreateClient();
}
这样,除了您在容器中调整的东西(mock/stubs/etc(之外,它将像在阶段一样全面启动服务。