不要在测试中验证 JWT 过期时间

Do not validate JWT expiration in tests

我正在使用 JWT 身份验证为我的 Web 服务编写集成测试。我想用从真实服务收到的令牌来测试它。问题是真正的令牌将在 1 小时后过期。

一种可能的方法是在下面的 class StartupAddJwtBearer 中设置 options.TokenValidationParameters.ValidateLifetime。 不过,Startup class也是一个待测代码,所以我不想更改或替换它来测试。

是否有一种巧妙的方法来测试除过期之外的所有 JWT 验证逻辑?

我的项目代码:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        services.AddAuthentication("JWT")
            .AddJwtBearer("JWT", options =>
            {
                options.Authority = "https://my-real-identity-server.com/";
                options.Audience = "...";
                // I don't want to disable lifetime validation in real life
                // options.TokenValidationParameters.ValidateLifetime = false;
            });
        
        // Other stuff
    }

    public void Configure(IApplicationBuilder app) => app
        .UseRouting()
        .UseAuthentication()
        .UseAuthorization()
        .UseEndpoints(endpoints => endpoints.MapControllers());
}

public class TestController : ControllerBase
{
    [Authorize]
    [HttpGet("/validate")]
    public string Get() => "success";
}

我的测试代码:

public class HostBuilderTests
{
    private IHost testHost;
    private CancellationTokenSource cancel;
    private HttpClient client;

    [SetUp]
    public async Task ShouldReturnStatus()
    {
        testHost = Host.CreateDefaultBuilder()
            .ConfigureWebHostDefaults(webBuilder =>
                webBuilder
                    .UseStartup<Startup>()
                    .UseTestServer())
            .ConfigureServices(services => services
                .AddLogging(b => b.ClearProviders().AddNUnit()))
            .Build();

        cancel = new CancellationTokenSource(10000);

        var server = testHost.Services.GetRequiredService<IServer>()
            .ShouldBeOfType<TestServer>();
        await testHost.StartAsync(cancel.Token);
        client = server.CreateClient();
    }

    [TearDown]
    public async Task TearDown()
    {
        await testHost.StopAsync(cancel.Token);

        client.Dispose();
        testHost.Dispose();
        cancel.Dispose();
    }

    [Test]
    [TestCase("<<JWT token copied from the real service>>")]
    public async Task StatusShouldBeOk(string realToken)
    {
        client.DefaultRequestHeaders.Authorization =
            new AuthenticationHeaderValue("Bearer", realToken);

        using var response = await client.GetAsync("/validate", cancel.Token);
        response.StatusCode.ShouldBe(HttpStatusCode.OK);
    }
}

最后我找到了一种管理身份验证处理程序选项的简单方法:

An authentication scheme is a name which corresponds to:

  • An authentication handler.
  • Options for configuring that specific instance of the handler.

MSDN

因此 post-configure JwtBearerOptions 指定身份验证方案名称 "JWT" 作为测试设置中的选项实例名称就足够了:

testHost = Host.CreateDefaultBuilder()
    // other setup
    .ConfigureServices(services => services
        .PostConfigure<JwtBearerOptions>("JWT", 
            op => op.TokenValidationParameters.ValidateLifetime = false)
        // other setup
    ).Build();

也可以传递 null 而不是 "JWT",因为它写在 comments:

// Null name is used to configure all named options.