You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 9集成测试中结合TimeProvider配置JWT认证——令牌验证因过期错误失败的解决方案

.NET 9集成测试中结合TimeProvider配置JWT认证——令牌验证因过期错误失败的解决方案

兄弟,看你贴的这段JWT认证配置代码,估计是在集成测试里踩了TimeProvider配合JWT验证的坑——明明模拟了令牌的有效期,结果还是触发了过期验证失败的错误对吧?我之前做.NET 9的集成测试时也碰到过一模一样的问题,给你整理下可行的解决步骤:

首先先看看你现有的配置(我帮你补全了部分缺失的代码结构):

public static void SetupAuthentication(this IServiceCollection services) {
    var authSettings = services.BuildServiceProvider().GetService<IOptionsSnapshot<AuthSettings>>()?.Value;

    // JWT Configuration
    services
        .AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                // 你的其他验证参数...
                ValidateLifetime = true
            };
        });
}

问题出在:默认情况下,JWT的TokenValidationParameters会用系统的默认时钟,不会自动使用你注册到DI容器里的TimeProvider。如果你在集成测试里用了FakeTimeProvider来模拟时间(比如测试令牌过期/有效场景),验证逻辑和你生成令牌时用的时间就对不上,自然就会判定令牌过期。

具体解决步骤:

  • 第一步:把TimeProvider注入到JWT验证参数中
    修改你的SetupAuthentication方法,确保在配置TokenValidationParameters时指定TimeProvider。这里推荐用AddJwtBearer的重载方法,直接从服务容器里获取依赖,避免提前调用BuildServiceProvider的潜在问题:
public static void SetupAuthentication(this IServiceCollection services)
{
    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    // 用带serviceProvider参数的重载
    .AddJwtBearer((serviceProvider, options) =>
    {
        var authSettings = serviceProvider.GetRequiredService<IOptionsSnapshot<AuthSettings>>().Value;
        // 从DI容器获取注册的TimeProvider
        var timeProvider = serviceProvider.GetRequiredService<TimeProvider>();

        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = authSettings.Issuer,
            ValidAudience = authSettings.Audience,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authSettings.SecretKey)),
            // 关键:把TimeProvider绑定到验证参数
            TimeProvider = timeProvider,
            // 测试场景下可以把ClockSkew设为0,减少时钟偏差的干扰
            ClockSkew = TimeSpan.Zero
        };
    });
}
  • 第二步:在集成测试中注册FakeTimeProvider并控制时间
    在你的测试类里,用.NET 8+自带的FakeTimeProvider来模拟时间,并且替换掉DI容器里默认的TimeProvider实现:
public class ProtectedEndpointTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;
    private readonly FakeTimeProvider _fakeTimeProvider;

    public ProtectedEndpointTests(WebApplicationFactory<Program> factory)
    {
        _fakeTimeProvider = new FakeTimeProvider();
        // 设置模拟的当前UTC时间,比如2024年1月1日
        _fakeTimeProvider.SetUtcNow(new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc));

        _factory = factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureServices(services =>
            {
                // 替换默认的TimeProvider为我们的模拟实例
                services.AddSingleton<TimeProvider>(_fakeTimeProvider);
            });
        });
    }

    [Fact]
    public async Task When_ValidTokenIsUsed_Should_ReturnOk()
    {
        // 用同一个FakeTimeProvider生成令牌的有效期
        var tokenHandler = new JwtSecurityTokenHandler();
        var secretKey = Encoding.UTF8.GetBytes("your-test-secret-key");
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, "test-user") }),
            // 令牌1小时后过期,用模拟的当前时间计算
            Expires = _fakeTimeProvider.GetUtcNow().AddHours(1),
            NotBefore = _fakeTimeProvider.GetUtcNow(),
            Issuer = "test-issuer",
            Audience = "test-audience",
            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(secretKey), SecurityAlgorithms.HmacSha256Signature)
        };
        var token = tokenHandler.CreateToken(tokenDescriptor);
        var tokenString = tokenHandler.WriteToken(token);

        // 创建测试客户端并添加令牌
        var client = _factory.CreateClient();
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenString);

        // 调用受保护的接口
        var response = await client.GetAsync("/api/protected");
        response.EnsureSuccessStatusCode();
    }
}

额外的优化建议:

  • 避免在配置方法中过早调用BuildServiceProvider,像上面那样用AddJwtBearer的重载直接从服务容器获取依赖,更符合DI的最佳实践,也能避免多次构建服务容器带来的性能问题。
  • 测试场景下建议把ClockSkew设为TimeSpan.Zero,默认的时钟偏差会给测试结果带来不确定性,关闭后验证逻辑会严格按照令牌的有效期来判断。

备注:内容来源于stack exchange,提问作者hiddenhenry

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.13 17:23:10