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

ASP.NET Core 5.0 WebAPI多认证方案下API Key校验失败不返回错误如何解决

问题根因

你遇到的问题核心是三个配置逻辑的问题:

  1. ApiKey认证方案没有自定义挑战失败的处理逻辑,校验失败后会自动回退到全局默认的OpenIdConnect挑战方案,而该方案的跳转逻辑不适用于API接口场景,不会直接返回401错误
  2. 你把认证方案转发逻辑写在了Cookie配置项中,仅当请求进入Cookie认证流程时才会触发转发,ApiKey认证失败后的流程不会关联到Cookie配置里的401返回逻辑
  3. 若你的接口未加[Authorize]特性,认证中间件只会尝试做认证但不会强制拦截未认证的请求,导致请求继续进入控制器逻辑

修改步骤

1. 给ApiKey认证Handler增加自定义失败处理逻辑

重写HandleChallengeAsync方法,校验失败时直接返回401错误信息,不走默认跳转流程:

public class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthenticationSchemeOptions>
{
    private const string APIKEY_NAME = "x-api-key";
    private const string APIKEY_VALUE = "sdflasuowerposaddfsadf1121234kjdsflkj";

    public ApiKeyAuthenticationHandler(IOptionsMonitor<ApiKeyAuthenticationSchemeOptions> options,
                                       ILoggerFactory logger,
                                       UrlEncoder encoder,
                                       ISystemClock clock) : base(options, logger, encoder, clock)
    {

    }

    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        string extractedApiKey = Request.Headers[APIKEY_NAME];
        // 增加空值判断避免无效比对
        if (string.IsNullOrWhiteSpace(extractedApiKey) || !APIKEY_VALUE.Equals(extractedApiKey))
        {
            return Task.FromResult(AuthenticateResult.Fail("Unauthorized client."));
        }

        var claims = new[]
        {
            new Claim("Permissions", "23")
        };

        var claimsIdentity = new ClaimsIdentity(claims, nameof(ApiKeyAuthenticationHandler));
        var authenticationTicket = new AuthenticationTicket(new ClaimsPrincipal(claimsIdentity), Scheme.Name);

        return Task.FromResult(AuthenticateResult.Success(authenticationTicket));
    }

    // 新增挑战失败处理逻辑
    protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
    {
        Response.StatusCode = StatusCodes.Status401Unauthorized;
        Response.ContentType = "application/json";
        // 可根据需求自定义返回格式
        await Response.WriteAsync("{\"message\":\"Unauthorized client.\"}");
    }

    // 可选:新增权限不足处理逻辑
    protected override async Task HandleForbiddenAsync(AuthenticationProperties properties)
    {
        Response.StatusCode = StatusCodes.Status403Forbidden;
        Response.ContentType = "application/json";
        await Response.WriteAsync("{\"message\":\"Access denied.\"}");
    }
}

2. 调整认证转发逻辑的位置

把ForwardDefaultSelector从Cookie配置项移到全局认证选项中,确保所有请求都会先触发方案转发判断:

public void ConfigureServices(IServiceCollection services)
{
    IAuthCookieValidate cookieEvent = new AuthCookieValidate();

    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        // 全局转发逻辑,所有请求先判断是否带ApiKey头
        options.ForwardDefaultSelector = context =>
        {
            return context.Request.Headers.ContainsKey(ApiConstants.APIKEY_NAME) ? ApiKeyAuthenticationDefaults.AuthenticationScheme : CookieAuthenticationDefaults.AuthenticationScheme;
        };
    })
    .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
    {
        options.Cookie.Name = ".Mgnt.AspNetCore.Cookies";
        options.ExpireTimeSpan = TimeSpan.FromDays(1);
        options.Events = new CookieAuthenticationEvents
        {
            OnRedirectToAccessDenied = context =>
            {
                context.Response.StatusCode = 403;
                return Task.CompletedTask;
            },
            OnRedirectToLogin = context =>
            {
                context.Response.StatusCode = 401;
                return Task.CompletedTask;
            },
            OnValidatePrincipal = cookieEvent.ValidateAsync
        };
        // 删掉原来写在这里的ForwardDefaultSelector
        options.Cookie.HttpOnly = true;
        options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
    })
    .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options => Configuration.Bind(OpenIdConnectDefaults.AuthenticationScheme, options))
    .AddApiKey();

    // 其他配置保持不变
    services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
    {
        options.Scope.Add("openid");
        options.Scope.Add("profile");
        options.Scope.Add("email");
    });
    
    services.AddSingleton<IAuthorizationPolicyProvider, AuthorizationPolicyProvider>();
    services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
}

3. 增加全局授权拦截(可选但推荐)

如果需要所有接口默认都需要认证,直接加全局授权过滤器,避免漏加[Authorize]特性导致未认证请求进入控制器:

services.AddControllers(options =>
{
    // 全局要求认证
    var policy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
    options.Filters.Add(new AuthorizeFilter(policy));
});

不需要认证的接口单独加[AllowAnonymous]特性即可。


优化建议

生产环境不要硬编码ApiKey的值,建议放到配置文件、配置中心或者密钥管理服务中,通过IOption读取使用。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 15:15:01