.NET 5 API如何验证关联API签发的Bearer Token实现鉴权
方案适配性判断
你提出的「在API1中编写自定义认证处理器,调用API2接口完成传入Token有效性校验」的方案完全适配当前架构场景,是作为非Token签发方的扩展服务做鉴权的标准实现方式,符合API2作为用户凭证唯一权威数据源的架构设计,能保证Token状态、用户权限的强一致性,避免本地校验时出现数据和权威源不同步的问题。
.NET 5 具体实现步骤
前置说明:实现前请先给项目添加
System.Net.Http.JsonNuget包,用于快速反序列化API2返回的用户信息。
- 第一步:定义自定义认证配置类,继承自
AuthenticationSchemeOptions,用于存放API2校验地址、缓存时长等可配置参数:
public class Api2AuthOptions : AuthenticationSchemeOptions { /// <summary> /// API2提供的Token有效性校验接口地址 /// </summary> public string Api2ValidateEndpoint { get; set; } /// <summary> /// 校验结果本地缓存时长(秒),用于降低API2调用压力 /// </summary> public int CacheExpireSeconds { get; set; } = 60; }
- 第二步:实现核心自定义认证处理器,继承自
AuthenticationHandler<Api2AuthOptions>,重写HandleAuthenticateAsync方法完成Token提取、校验、身份组装逻辑:
public class Api2AuthHandler : AuthenticationHandler<Api2AuthOptions> { private readonly IHttpClientFactory _httpClientFactory; private readonly IMemoryCache _cache; private const string BearerPrefix = "Bearer "; public Api2AuthHandler(IOptionsMonitor<Api2AuthOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IHttpClientFactory httpClientFactory, IMemoryCache cache) : base(options, logger, encoder, clock) { _httpClientFactory = httpClientFactory; _cache = cache; } protected override async Task<AuthenticateResult> HandleAuthenticateAsync() { // 从请求头提取Authorization字段 if (!Request.Headers.TryGetValue("Authorization", out var authHeader)) { return AuthenticateResult.NoResult(); } var authValue = authHeader.ToString(); if (!authValue.StartsWith(BearerPrefix, StringComparison.OrdinalIgnoreCase)) { return AuthenticateResult.NoResult(); } var token = authValue.Substring(BearerPrefix.Length).Trim(); if (string.IsNullOrWhiteSpace(token)) { return AuthenticateResult.Fail("Token格式无效"); } // 优先读取本地缓存,避免重复调用API2 var cacheKey = $"api2_token_{token}"; if (_cache.TryGetValue<ClaimsPrincipal>(cacheKey, out var cachedPrincipal)) { var cachedTicket = new AuthenticationTicket(cachedPrincipal, Scheme.Name); return AuthenticateResult.Success(cachedTicket); } try { // 调用API2校验接口 var httpClient = _httpClientFactory.CreateClient(); var validateReq = new HttpRequestMessage(HttpMethod.Get, Options.Api2ValidateEndpoint); validateReq.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); var response = await httpClient.SendAsync(validateReq); if (!response.IsSuccessStatusCode) { return AuthenticateResult.Fail("Token已失效或无权限"); } // 反序列化API2返回的用户信息(包含用户ID、角色、权限点等声明) var userInfo = await response.Content.ReadFromJsonAsync<Api2UserDto>(); // 组装身份声明 var claims = new List<Claim> { new Claim(ClaimTypes.NameIdentifier, userInfo.UserId.ToString()), new Claim(ClaimTypes.Name, userInfo.UserName) }; claims.AddRange(userInfo.Roles.Select(role => new Claim(ClaimTypes.Role, role))); claims.AddRange(userInfo.Permissions.Select(perm => new Claim("permission", perm))); var identity = new ClaimsIdentity(claims, Scheme.Name); var principal = new ClaimsPrincipal(identity); var ticket = new AuthenticationTicket(principal, Scheme.Name); // 写入本地缓存 _cache.Set(cacheKey, principal, TimeSpan.FromSeconds(Options.CacheExpireSeconds)); return AuthenticateResult.Success(ticket); } catch (Exception ex) { return AuthenticateResult.Fail($"认证服务暂不可用:{ex.Message}"); } } } /// <summary> /// 对应API2校验接口返回的用户信息结构 /// </summary> public class Api2UserDto { public long UserId { get; set; } public string UserName { get; set; } public List<string> Roles { get; set; } = new List<string>(); public List<string> Permissions { get; set; } = new List<string>(); }
- 第三步:在
Startup.cs的ConfigureServices方法中注册认证服务、依赖组件:
public void ConfigureServices(IServiceCollection services) { // 注册缓存、HttpClient依赖 services.AddMemoryCache(); services.AddHttpClient(); // 注册自定义认证方案 services.AddAuthentication("Api2Bearer") .AddScheme<Api2AuthOptions, Api2AuthHandler>("Api2Bearer", options => { options.Api2ValidateEndpoint = Configuration["Api2Config:ValidateEndpoint"]; options.CacheExpireSeconds = int.Parse(Configuration["Api2Config:CacheSeconds"] ?? "60"); }); services.AddControllers(options => { // 全局配置授权策略,默认所有接口需要认证后访问 var defaultPolicy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .Build(); options.Filters.Add(new AuthorizeFilter(defaultPolicy)); }); }
- 第四步:在
Startup.cs的Configure方法中按正确顺序添加认证、授权中间件(注意顺序不能颠倒,否则认证逻辑不会生效):
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // 其他前置中间件:异常处理、HTTPS重定向、静态文件等 app.UseRouting(); // 认证中间件必须放在UseRouting之后、UseAuthorization/MapControllers之前 app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
可选优化与替代方案
- 性能优化:如果业务对Token吊销、权限变更的实时性要求不高,可以适当延长本地缓存时长,进一步降低API2的调用压力;如果要求强一致性,可以缩短缓存时长,或订阅API2的Token吊销、权限变更事件主动清理对应缓存。
- 高可用优化:给调用API2的HttpClient添加熔断、超时策略,避免API2故障时API1所有接口完全不可用。
- 高性能替代方案:如果API2签发的是标准JWT,且对外暴露了签名公钥或OIDC发现文档,也可以直接使用.NET内置的
JwtBearerHandler做本地签名校验,不需要每次请求都调用API2,性能更高。但该方案无法实时感知Token状态变更,需要配合短Token有效期+刷新Token机制使用,可根据业务一致性要求选择。
内容的提问来源于stack exchange,提问作者user928112
相关产品推荐
相关产品推荐

