Skip JWT Auth during Tests ASP.Net Core 3.1 Web Api

Shk*_*ori 18 c# authentication jwt asp.net-core asp.net-core-3.1

I a have a very simple app with one JWT authenticated controller:

[ApiController]
[Authorize]
[Route("[controller]")]
public class JwtController : ControllerBase
{

    public JwtController() { }

    [HttpGet]
    public ActionResult Get() => Ok("Working!");
}
Run Code Online (Sandbox Code Playgroud)

With the authentication configured as:

services.AddAuthentication(x =>
{
    x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
    x.RequireHttpsMetadata = false;
    x.SaveToken = true;
    x.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = false,
        ValidateAudience = false
    };
});
Run Code Online (Sandbox Code Playgroud)

During tests, i want the user to be "authenticated" all the time so that [Authorize] would be skipped.

[Fact]
public async Task JwtIsSkipped()
{
    var response = (await _Client.GetAsync("/jwt")).EnsureSuccessStatusCode();
    var stringResponse = await response.Content.ReadAsStringAsync();

    Assert.Equal("Working!", stringResponse);
}
Run Code Online (Sandbox Code Playgroud)

Running the test like this will fail, so following this doc I added this simple auth handler:

public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    public const string DefaultScheme = "Test";
    public TestAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
        ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
        : base(options, logger, encoder, clock)
    {
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var claims = new[] { new Claim(ClaimTypes.Name, "Test user") };
        var identity = new ClaimsIdentity(claims, DefaultScheme);
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, DefaultScheme);

        return Task.FromResult(AuthenticateResult.Success(ticket));
    }
}
Run Code Online (Sandbox Code Playgroud)

So now my test class looks like this:

public class UnitTest : IClassFixture<WebApplicationFactory<Startup>>
{
    private readonly WebApplicationFactory<Startup> _Factory;
    private readonly HttpClient _Client;

    public UnitTest(WebApplicationFactory<Startup> factory)
    {
        _Factory = factory;
        _Client = _Factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddAuthentication(TestAuthHandler.DefaultScheme)
                        .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
                            TestAuthHandler.DefaultScheme, options => { });
            });
        }).CreateClient();

        _Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(TestAuthHandler.DefaultScheme);
    }

    [Fact]
    public async Task JwtIsSkipped()
    {
        var response = (await _Client.GetAsync("/jwt")).EnsureSuccessStatusCode();
        var stringResponse = await response.Content.ReadAsStringAsync();

        Assert.Equal("Working!", stringResponse);
    }
}
Run Code Online (Sandbox Code Playgroud)

And it still fails, I have no idea what I'm doing wrong.

may*_*ʎɐɯ 11

我之前在 Microsoft 示例中遇到过类似情况,并且可以向您保证它会让人头疼,它可能适用于特定的 Core 版本,但我已经放弃了。我是这样解决的。

我的目标是在测试时授权系统,而不是AddAuthentication在我们的测试中使用,我们只是创建一个FakePolicyEvaluator类并将其作为单例添加到我们的测试中。

所以让我们进入我们的 FakePolicyEvaluator 类:

public class FakePolicyEvaluator : IPolicyEvaluator
{
    public virtual async Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)
    {
        var principal = new ClaimsPrincipal();
        principal.AddIdentity(new ClaimsIdentity(new[] {
            new Claim("Permission", "CanViewPage"),
            new Claim("Manager", "yes"),
            new Claim(ClaimTypes.Role, "Administrator"),
            new Claim(ClaimTypes.NameIdentifier, "John")
        }, "FakeScheme"));
        return await Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(principal,
            new AuthenticationProperties(), "FakeScheme")));
    }

    public virtual async Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy,
        AuthenticateResult authenticationResult, HttpContext context, object resource)
    {
        return await Task.FromResult(PolicyAuthorizationResult.Success());
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在我们的ConfigureTestServices我们添加services.AddSingleton<IPolicyEvaluator, FakePolicyEvaluator>();

所以在你的测试代码中是这样的:

private readonly HttpClient _client;

public UnitTest(WebApplicationFactory<Startup> factory)
{
    _client = factory.WithWebHostBuilder(builder =>
    {
        builder.ConfigureTestServices(services =>
        {
            services.AddSingleton<IPolicyEvaluator, FakePolicyEvaluator>();
        });
    }).CreateClient();
}

[Fact]
public async Task JwtIsSkipped()
{
    var response = (await _client.GetAsync("/jwt")).EnsureSuccessStatusCode();
    var stringResponse = await response.Content.ReadAsStringAsync();

    Assert.Equal("Working!", stringResponse);
}
Run Code Online (Sandbox Code Playgroud)

这就对了。现在,当您进行测试时,它将绕过身份验证。我已经使用提供的控制器对其进行了测试,并且可以正常工作。

也可以将伪造放在应用程序启动中,它既可用于测试,又可在开发环境下工作。检查参考文章。

免责声明:我在我的个人网站参考中写了更深入的文章,您可以下载此https://github.com/maythamfahmi/BlogExamples/tree/master/BypassAuthorization的 github 版本

  • ClaimsPrincipal 中缺少 Claims。如果您检查“_httpContext.HttpContext.User”,您会发现其中不包含任何声明 (3认同)

Kah*_*azi 8

你需要设置 DefaultAuthenticateScheme

builder.ConfigureTestServices(services =>
{
    services.AddAuthentication(options =>
    {
        x.DefaultAuthenticateScheme = TestAuthHandler.DefaultScheme;
        x.DefaultScheme = TestAuthHandler.DefaultScheme;
    }).AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
           TestAuthHandler.DefaultScheme, options => { });
});
Run Code Online (Sandbox Code Playgroud)