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

.NET 6 Web API同时支持JWT与API Key认证遇401错误求助

搞定JWT+API Key双认证的401问题

问题根源

你当前的配置里,默认认证和挑战方案都是JWT Bearer。当使用不带Bearer前缀的API Key请求时,JWT认证会直接失败,触发默认的401错误。同时你自定义的中间件逻辑存在多处问题,导致API Key认证无法正常生效。


一步步解决方法

1. 修改认证配置,支持双认证方案

调整Startup.cs中的认证服务配置,添加API Key认证方案,并让系统自动判断使用哪种认证方式:

services.AddAuthentication(options =>
{
    // 设置默认认证为"多方案",让系统依次尝试JWT和API Key
    options.DefaultAuthenticateScheme = "MultiScheme";
    options.DefaultChallengeScheme = "MultiScheme";
})
// 保留原有JWT认证配置
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        // 你的JWT验证参数,示例如下
        ValidateIssuer = true,
        ValidIssuer = "你的Issuer地址",
        ValidateAudience = true,
        ValidAudience = "你的Audience标识",
        ValidateLifetime = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("你的密钥字符串"))
    };
})
// 添加API Key认证方案,后续用自定义处理器实现逻辑
.AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>("ApiKeyScheme", options => { });

// 配置多方案的路由逻辑
services.AddAuthentication()
    .AddPolicyScheme("MultiScheme", "MultiScheme", options =>
    {
        options.ForwardDefaultSelector = context =>
        {
            var authHeader = context.Request.Headers["Authorization"].FirstOrDefault();
            // 带Bearer前缀的请求用JWT,其他用API Key
            return !string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("Bearer ") 
                ? JwtBearerDefaults.AuthenticationScheme 
                : "ApiKeyScheme";
        };
    });

2. 实现API Key认证处理器(替代自定义中间件)

用ASP.NET Core标准的认证处理器替代原自定义中间件,更符合框架规范:

public class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthenticationOptions>
{
    private readonly IAuthService _authService;

    public ApiKeyAuthenticationHandler(
        IOptionsMonitor<ApiKeyAuthenticationOptions> options,
        ILoggerFactory logger,
        UrlEncoder encoder,
        ISystemClock clock,
        IAuthService authService)
        : base(options, logger, encoder, clock)
    {
        _authService = authService;
    }

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        // 读取Authorization请求头
        if (!Request.Headers.TryGetValue("Authorization", out var authHeader))
        {
            return AuthenticateResult.NoResult();
        }

        string apiKey = authHeader.ToString().Trim();
        if (string.IsNullOrEmpty(apiKey))
        {
            return AuthenticateResult.Fail("API Key不能为空");
        }

        // 校验API Key是否为合法GUID格式
        if (!Guid.TryParse(apiKey, out var apiKeyGuid))
        {
            return AuthenticateResult.Fail("API Key格式无效");
        }

        // 复用你原有的API Key有效性校验逻辑
        if (!IsValidPat(apiKey))
        {
            return AuthenticateResult.Fail("API Key无效");
        }

        // 从数据库获取用户关联的Claims
        var claims = await _authService.GetClaimsForPat(apiKeyGuid);
        if (claims == null)
        {
            return AuthenticateResult.Fail("该API Key未关联任何用户");
        }

        // 组装用户身份Claims
        var claimsToInclude = new List<Claim>(claims)
        {
            new Claim(ClaimTypes.AuthenticationMethod, "ApiKey")
            // 这里添加你需要的额外自定义Claims
        };

        var identity = new ClaimsIdentity(claimsToInclude, Scheme.Name);
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, Scheme.Name);

        return AuthenticateResult.Success(ticket);
    }

    // 复制你原有的IsValidPat方法逻辑
    private bool IsValidPat(string token)
    {
        // 示例:检查API Key是否存在于数据库
        return true;
    }
}

// 配套的认证选项类,无需修改内容
public class ApiKeyAuthenticationOptions : AuthenticationSchemeOptions { }

3. 调整中间件顺序与授权策略

移除原自定义中间件,恢复标准的中间件执行顺序:

app.UseCors(ourOrigins);

app.UseAuthentication();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});

同时确保PatAuthorization授权策略已在ConfigureServices中正确配置:

services.AddAuthorization(options =>
{
    options.AddPolicy("PatAuthorization", policy =>
    {
        // 示例:要求用户拥有指定权限Claim
        policy.RequireClaim("Permission", "AccessApi");
    });
});

4. 控制器指定支持的认证方案

在需要支持双认证的控制器或Action上,标注允许的认证方案:

[Authorize(AuthenticationSchemes = "MultiScheme")]
[ApiController]
[Route("api/[controller]")]
public class YourController : ControllerBase
{
    // 针对特定Action可单独指定授权策略
    [Authorize(Policy = "PatAuthorization")]
    [HttpGet]
    public IActionResult Get()
    {
        return Ok();
    }
}

原自定义中间件的问题点

如果坚持使用原中间件,需修复以下问题:

  • 中间件顺序错误:PatAuthenticationMiddleware必须放在UseAuthentication之前,否则JWT认证失败会直接返回401,中间件无法触发。
  • User对象处理错误:JWT认证失败时context.User是匿名用户,需新建ClaimsPrincipal而非往现有User中添加Identity。
  • 授权逻辑位置错误:不要在中间件中提前返回403,应交给UseAuthorization中间件处理,符合ASP.NET Core的授权流程。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 00:01:07