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

在WebApplicationFactory<Program>的XUnit测试中Mock JWT令牌验证控制器

ASP.NET Core 6集成测试:模拟JWT授权测试带权限控制器

1. 自定义WebApplicationFactory,替换认证配置

继承WebApplicationFactory<Program>后,在ConfigureWebHost方法中替换原有JWT认证服务,改用测试专用的认证方案,直接注入自定义Claims,无需真实签发JWT令牌:

public class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            // 移除原认证服务配置
            var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(IAuthenticationService));
            if (descriptor != null)
            {
                services.Remove(descriptor);
            }

            // 添加测试用认证服务
            services.AddAuthentication("TestScheme")
                .AddScheme<AuthenticationSchemeOptions, TestAuthenticationHandler>("TestScheme", options => { });
        });

        // 保留原有内存数据库等测试配置
        base.ConfigureWebHost(builder);
    }
}

// 自定义测试认证处理器,直接读取预定义的ClaimsPrincipal
public class TestAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    public TestAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
        : base(options, logger, encoder, clock)
    {
    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        if (Context.Items.TryGetValue("TestUser", out var user) && user is ClaimsPrincipal claimsPrincipal)
        {
            var ticket = new AuthenticationTicket(claimsPrincipal, Scheme.Name);
            return Task.FromResult(AuthenticateResult.Success(ticket));
        }

        return Task.FromResult(AuthenticateResult.Fail("No test user provided"));
    }
}

2. 扩展HttpClient,快速添加测试用户Claims

创建扩展方法,方便给HttpClient附加不同权限的ClaimsPrincipal:

public static class HttpClientExtensions
{
    public static HttpClient WithTestUser(this HttpClient client, params Claim[] claims)
    {
        var claimsIdentity = new ClaimsIdentity(claims, "TestScheme");
        var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);

        // 通过自定义Handler将ClaimsPrincipal注入HttpContext
        var handler = new TestRequestHandler(claimsPrincipal)
        {
            InnerHandler = new HttpClientHandler()
        };

        return new HttpClient(handler)
        {
            BaseAddress = client.BaseAddress
        };
    }
}

public class TestRequestHandler : DelegatingHandler
{
    private readonly ClaimsPrincipal _testUser;

    public TestRequestHandler(ClaimsPrincipal testUser)
    {
        _testUser = testUser;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (request.Properties.TryGetValue("Microsoft.AspNetCore.Http.HttpContext", out var contextObj) && contextObj is HttpContext context)
        {
            context.Items["TestUser"] = _testUser;
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

3. 编写测试方法,验证不同权限场景

针对[Authorize(Roles = "User")]和[Authorize(Policy = "IsConfirmed")]的控制器,生成对应Claims的测试用户,验证请求结果:

public class AuthorizedControllerTests : IClassFixture<CustomWebApplicationFactory>
{
    private readonly HttpClient _client;

    public AuthorizedControllerTests(CustomWebApplicationFactory factory)
    {
        _client = factory.CreateClient();
    }

    [Fact]
    public async Task Get_WithUserRoleAndConfirmedClaim_ReturnsOk()
    {
        var authorizedClient = _client.WithTestUser(
            new Claim(ClaimTypes.Role, "User"),
            new Claim("IsConfirmed", "true") // 匹配自定义策略的Claim,需和项目中策略定义一致
        );

        var response = await authorizedClient.GetAsync("/api/authorized/confirmed-user");
        
        response.EnsureSuccessStatusCode();
    }

    [Fact]
    public async Task Get_OnlyUserRoleWithoutConfirmedClaim_ReturnsForbidden()
    {
        var unconfirmedClient = _client.WithTestUser(
            new Claim(ClaimTypes.Role, "User")
        );

        var response = await unconfirmedClient.GetAsync("/api/authorized/confirmed-user");
        
        Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
    }

    [Fact]
    public async Task Get_WithoutUserRole_ReturnsUnauthorized()
    {
        var anonymousClient = _client.WithTestUser();

        var response = await anonymousClient.GetAsync("/api/authorized/user-only");
        
        Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
    }
}

可选:模拟真实JWT令牌授权

如果需要完全模拟真实JWT流程,可直接生成签名令牌并添加到请求头:

var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes("your-test-jwt-secret"); // 需和项目配置中的JWT密钥一致
var tokenDescriptor = new SecurityTokenDescriptor
{
    Subject = new ClaimsIdentity(new[]
    {
        new Claim(ClaimTypes.Role, "User"),
        new Claim("IsConfirmed", "true")
    }),
    Expires = DateTime.UtcNow.AddHours(1),
    SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);

_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenString);

内容的提问来源于stack exchange,提问作者Silny ToJa

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 05:00:58