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

.NET 5 API如何验证关联API签发的Bearer Token实现鉴权

方案适配性判断

你提出的「在API1中编写自定义认证处理器,调用API2接口完成传入Token有效性校验」的方案完全适配当前架构场景,是作为非Token签发方的扩展服务做鉴权的标准实现方式,符合API2作为用户凭证唯一权威数据源的架构设计,能保证Token状态、用户权限的强一致性,避免本地校验时出现数据和权威源不同步的问题。

.NET 5 具体实现步骤

前置说明:实现前请先给项目添加System.Net.Http.Json Nuget包,用于快速反序列化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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 03:42:18