使用已指定的身份验证方案集成测试授权端点

Jac*_*son 1 c# integration-testing authorization asp.net-core-webapi .net-6.0

我正在尝试使用授权端点为 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();
    });
}
Run Code Online (Sandbox Code Playgroud)

如果您使用的是默认身份验证方案,则可以很好地工作,您可以在集成测试启动时更改该方案以使用测试方案。但是,我的授权端点正在使用指定的AuthenticationSchemes,因此测试方案永远不会被端点授权。例如

[Authorize(AuthenticationSchemes = "Scheme1,Scheme2")]
public class AppVersionController : ControllerBase
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

我可以通过在测试时指定环境变量、检查它并将测试方案动态添加到授权端点来解决这个问题。然而,这为应用程序添加了许多特定于测试的逻辑,这在主项目中并不好。

这会起作用:

// Test scheme added dynamically from an environment variable to get the below result
[Authorize(AuthenticationSchemes = "Scheme1,Scheme2,Test")]
public class AppVersionController : ControllerBase
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

我通过创建一个自定义属性来完成此操作,该属性基本上如下所示:

public class AuthorizeAll : AuthorizeAttribute
{
    public AuthorizeAll()
    {
        var authenticationSchemes = "Scheme1,Scheme2";
        if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Testing")
        {
            authenticationSchemes += ",Test";
        }
        AuthenticationSchemes = authenticationSchemes;
    }
}
Run Code Online (Sandbox Code Playgroud)

我只是不喜欢我们必须继续在应用程序层维护这种测试身份验证方案以及这种方法的安全问题。

问题

设置特定身份验证方案时,为 .NET 集成测试授权端点的最佳方法是什么?

在进行单元测试以运行测试需要工作的特定逻辑时检查应用程序中的环境变量是一个好习惯吗?

目前使用的主要身份验证方案是使用 JWT,那么有没有更好的方法来模拟 JWT 进行测试呢?

wei*_*hch 5

您可以通过多种方式模拟身份验证(从授权策略触发)。根据您的应用程序设置,您可能会选择最适合您的应用程序。

1.使用默认策略

当您[Authorize(AuthenticationSchemes = "Scheme1,Scheme2")]在操作/控制器上使用时,在评估之前会合并默认授权策略。在测试设置中,您可以将空策略与测试方案与实际代码中的默认策略结合起来,以确保您的测试方案始终运行。

builder.ConfigureTestServices(services =>
{
    services.AddAuthorization(opt =>
    {
        opt.DefaultPolicy = new AuthorizationPolicyBuilder()
            .AddAuthenticationSchemes("Test")
            .Combine(opt.DefaultPolicy)
            .Build();
    });
});
Run Code Online (Sandbox Code Playgroud)

上面的代码将使用三个策略来验证请求:Scheme1Scheme2Test。如果您也有的话,这对您不起作用[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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后将其添加到您的测试设置中:

builder.ConfigureTestServices(services =>
{
    services.AddTransient<IPolicyEvaluator>(serviceProvider => new TestPolicyEvaluator(
        ActivatorUtilities.CreateInstance<PolicyEvaluator>(serviceProvider)));
});
Run Code Online (Sandbox Code Playgroud)

这与默认策略解决方案的工作原理类似,并且应该适用于所有策略评估场景。

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"));
    }
}
Run Code Online (Sandbox Code Playgroud)

假设您有一个名为“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());
    });
});
Run Code Online (Sandbox Code Playgroud)