如何让ASP.NET Core接口支持API密钥或Bearer令牌双认证?
解决方案:让ASP.NET Core 8 Web API接口支持Bearer令牌或API密钥任意一种认证
核心思路
问题出在你同时使用多个独立的[Authorize]属性时,ASP.NET Core默认只会尝试第一个认证方案,导致Bearer令牌的声明无法被正确识别。正确的做法是创建一个包含两种认证方案的自定义授权策略,让框架自动尝试两种认证方式,只要其中一种通过即可进入权限校验环节。
步骤1:确保两种认证方案注册时指定明确名称
在Program.cs中注册认证服务时,给API密钥认证指定唯一方案名(比如"ApiKey"),Bearer令牌使用默认的JwtBearerDefaults.AuthenticationScheme即可:
// 注册Bearer令牌认证 builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { // 你的Bearer配置(密钥、 issuer、audience等) }); // 注册API密钥认证(假设你已经实现了ApiKeyAuthenticationHandler) builder.Services.AddAuthentication() .AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>("ApiKey", options => { // 你的API密钥配置(比如密钥存储位置、验证逻辑等) });
步骤2:创建支持双认证的自定义授权策略
在Program.cs中添加授权策略,同时包含两种认证方案,并添加必要的权限校验要求:
builder.Services.AddAuthorization(options => { options.AddPolicy("AllowBearerOrApiKey", policy => { // 添加两种认证方案,框架会依次尝试,只要一种通过认证即可 policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme); policy.AuthenticationSchemes.Add("ApiKey"); // 要求用户必须通过认证(两种方式任意一种) policy.RequireAuthenticatedUser(); // 如果需要自定义权限校验,添加你的授权要求(比如之前的AuthorizationHandler对应的Requirement) policy.Requirements.Add(new YourCustomPermissionRequirement()); }); });
步骤3:调整API密钥认证的ClaimsPrincipal生成
确保你的ApiKeyAuthenticationHandler在验证通过后,生成包含必要声明的ClaimsPrincipal,这样授权逻辑可以统一处理两种认证用户:
protected override async Task<AuthenticateResult> HandleAuthenticateAsync() { // 你的API密钥验证逻辑(从Header/Query获取密钥、校验有效性) if (!TryGetApiKey(out var apiKey) || !await ValidateApiKey(apiKey)) { return AuthenticateResult.Fail("Invalid API Key"); } // 生成API密钥用户的声明(比如添加角色、用户ID等) var claims = new[] { new Claim(ClaimTypes.NameIdentifier, apiKeyOwnerId), new Claim(ClaimTypes.Role, "ApiClient") // 标记为API密钥用户,方便授权校验 }; var identity = new ClaimsIdentity(claims, Scheme.Name); var principal = new ClaimsPrincipal(identity); return AuthenticateResult.Success(new AuthenticationTicket(principal, Scheme.Name)); }
步骤4:调整AuthorizationHandler支持双认证用户
修改你的自定义AuthorizationHandler,使其能同时处理Bearer令牌用户和API密钥用户的权限校验:
public class YourCustomPermissionHandler : AuthorizationHandler<YourCustomPermissionRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, YourCustomPermissionRequirement requirement) { var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier); if (string.IsNullOrEmpty(userId)) { context.Fail(); return Task.CompletedTask; } // 从HttpContext获取路由参数(比如客户ID) if (context.Resource is HttpContext httpContext) { var customerId = httpContext.Request.RouteValues["id"]?.ToString(); // 权限校验逻辑: // 1. Bearer用户:校验用户是否有权访问该客户数据 // 2. API密钥用户:直接通过(或根据你的业务逻辑校验) if (IsBearerUserAuthorized(userId, customerId) || context.User.IsInRole("ApiClient")) { context.Succeed(requirement); return Task.CompletedTask; } } context.Fail(); return Task.CompletedTask; } // 实现Bearer用户的权限校验逻辑 private bool IsBearerUserAuthorized(string userId, string customerId) { // 你的业务逻辑:比如用户ID与客户ID关联校验 return userId == customerId; } }
步骤5:在接口上应用自定义策略
将接口上的多个[Authorize]属性替换为自定义策略:
[ApiController] [Route("api/customer")] public class CustomerController : ControllerBase { [HttpGet("{id}")] [Authorize(Policy = "AllowBearerOrApiKey")] public IActionResult GetCustomer(string id) { // 你的接口逻辑 return Ok(new { CustomerId = id }); } }
关键注意事项
- 确保中间件顺序正确:
app.UseAuthentication()必须在app.UseAuthorization()之前 - 测试两种认证方式:分别用有效Bearer令牌、有效API密钥访问接口,确认都能正常返回数据;无认证信息时返回403
- 避免重复端点:通过策略复用同一个接口,无需创建冗余路由
内容的提问来源于stack exchange,提问作者sfaust
相关产品推荐
相关产品推荐

