ASP.NET Core 5.0 WebAPI多认证方案下API Key校验失败不返回错误如何解决
问题根因
你遇到的问题核心是三个配置逻辑的问题:
- ApiKey认证方案没有自定义挑战失败的处理逻辑,校验失败后会自动回退到全局默认的
OpenIdConnect挑战方案,而该方案的跳转逻辑不适用于API接口场景,不会直接返回401错误 - 你把认证方案转发逻辑写在了Cookie配置项中,仅当请求进入Cookie认证流程时才会触发转发,ApiKey认证失败后的流程不会关联到Cookie配置里的401返回逻辑
- 若你的接口未加
[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
相关产品推荐
相关产品推荐

