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

.NET集成测试:指定认证方案的授权端点测试及JWT模拟问题

ASP.NET Web API 集成测试授权端点问题

我正在为带有授权端点的ASP.NET Web API设置集成测试,之前参考微软文档添加了模拟认证,让测试客户端能访问授权端点:

builder.ConfigureTestServices(services =>
{
    services.AddAuthorization(options =>
    {
        options.DefaultPolicy = new AuthorizationPolicyBuilder()
            .AddAuthenticationSchemes("Test")
            .RequireAuthenticatedUser()
            .Build();
    });
}

这个方案在使用默认认证方案时可行,但我的授权端点明确指定了AuthenticationSchemes,测试方案无法通过授权:

[Authorize(AuthenticationSchemes = "Scheme1,Scheme2")]
public class AppVersionController : ControllerBase
{
    ...
}

我试过通过环境变量动态把测试方案添加到授权端点,用自定义属性实现:

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

但这种方式需要在业务层维护测试认证方案,有维护成本和安全顾虑,所以想咨询三个问题:

  1. 指定认证方案时,.NET集成测试中授权端点的最佳测试方式是什么?
  2. 单元测试中通过检查环境变量执行测试专属逻辑是否为最佳实践?
  3. 当前主认证方案为JWT,是否有更优的JWT模拟测试方案?

问题1:指定认证方案时,.NET集成测试中授权端点的最佳测试方式

完全不用修改业务代码,直接在测试项目的ConfigureTestServices里扩展或替换授权策略即可,推荐两种方案:

方案一:直接扩展目标授权策略

如果控制器是通过指定AuthenticationSchemes生成的临时策略,或者有明确的自定义策略,可以在测试配置里直接修改,把测试认证方案加入其中:

builder.ConfigureTestServices(services =>
{
    // 注册Test认证方案的处理器(需要自己实现TestAuthHandler,逻辑是直接通过认证)
    services.AddAuthentication("Test")
        .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>("Test", options => { });

    services.AddAuthorization(options =>
    {
        // 覆盖默认策略,加入Test方案
        options.DefaultPolicy = new AuthorizationPolicyBuilder()
            .AddAuthenticationSchemes("Scheme1", "Scheme2", "Test")
            .RequireAuthenticatedUser()
            .Build();

        // 如果是自定义策略,直接修改对应策略
        options.AddPolicy("CustomSchemePolicy", policy =>
        {
            policy.AddAuthenticationSchemes("Scheme1", "Scheme2", "Test")
                  .RequireAuthenticatedUser();
        });
    });
});

方案二:自定义授权策略提供器

如果不想逐个修改策略,可以写一个全局的策略提供器,自动给所有授权策略添加Test认证方案:

public class TestAuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider
{
    public TestAuthorizationPolicyProvider(IOptions<AuthorizationOptions> options) 
        : base(options) { }

    public override async Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
    {
        var policy = await base.GetPolicyAsync(policyName);
        if (policy == null) return null;

        // 将Test方案追加到原有认证方案列表并去重
        var newSchemes = policy.AuthenticationSchemes.Append("Test").Distinct();
        return new AuthorizationPolicyBuilder(newSchemes)
            .Combine(policy)
            .Build();
    }
}

然后在测试配置里注册这个提供器:

builder.ConfigureTestServices(services =>
{
    services.AddSingleton<IAuthorizationPolicyProvider, TestAuthorizationPolicyProvider>();
    
    // 注册Test认证方案
    services.AddAuthentication("Test")
        .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>("Test", options => { });
});

问题2:单元测试中通过检查环境变量执行测试专属逻辑是否为最佳实践

这不是最佳实践,主要问题有:

  • 维护成本高:业务代码混入测试逻辑,后续修改测试方案或环境配置都要改动业务代码
  • 安全风险:如果测试环境的配置意外流入生产,可能导致未授权访问漏洞
  • 测试稳定性差:环境变量属于外部依赖,会让测试结果受外部配置影响

更好的做法:

  • 集成测试:通过测试项目的ConfigureTestServices覆盖或扩展服务,完全隔离业务代码和测试逻辑
  • 单元测试:用依赖注入替换认证相关依赖,比如Mock认证服务,或者使用测试专属的配置类

问题3:JWT模拟测试的更优方案

不用自定义Test认证方案,直接模拟合法JWT令牌即可,推荐两种常用方式:

方案一:配置测试专用JWT验证

在测试项目里重新配置JWT认证,使用测试专属的密钥、Issuer和Audience,生成符合要求的JWT令牌:

builder.ConfigureTestServices(services =>
{
    // 移除原有JWT配置,避免冲突
    services.RemoveAll<JwtBearerOptions>();
    
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = "test-issuer",
                ValidAudience = "test-audience",
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("test-secret-key-1234567890"))
            };
        });
});

测试时生成令牌并添加到请求头:

// 生成测试JWT令牌
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.UTF8.GetBytes("test-secret-key-1234567890");
var tokenDescriptor = new SecurityTokenDescriptor
{
    Subject = new ClaimsIdentity(new[]
    {
        new Claim(ClaimTypes.Name, "test-user"),
        new Claim(ClaimTypes.Role, "admin")
    }),
    Expires = DateTime.UtcNow.AddHours(1),
    Issuer = "test-issuer",
    Audience = "test-audience",
    SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var jwtToken = tokenHandler.WriteToken(token);

// 发送请求
var client = factory.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", jwtToken);
var response = await client.GetAsync("/api/appversion");

方案二:使用官方MockJwtBearer(ASP.NET Core 8+)

如果使用ASP.NET Core 8及以上版本,可以用官方提供的MockJwtBearer简化配置:

  1. 安装NuGet包:Microsoft.AspNetCore.Authentication.JwtBearer.Testing
  2. 测试配置里注册:
builder.ConfigureTestServices(services =>
{
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddMockJwtBearer();
});
  1. 测试时直接生成令牌:
var client = factory.CreateClient();
var mockBearer = factory.Services.GetRequiredService<IMockJwtBearer>();
mockBearer.SetBearerToken(new[]
{
    new Claim(ClaimTypes.Name, "test-user"),
    new Claim(ClaimTypes.Role, "admin")
});

var response = await client.GetAsync("/api/appversion");

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 01:10:29