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

.NET 6中间件ApiKey与JWT认证的令牌传递方案咨询

适配高并发的JWT令牌传递方案

核心问题分析

  • 采用JwtTokenStore(AddTransient注入):每次请求会生成新实例,无法跨组件共享当前请求的有效JWT
  • 使用ConcurrentQueue:全局共享容器,并发请求时会出现令牌串用的问题

正确实现方案

方案1:利用HttpContext.Items存储请求级JWT

HttpContext.Items是请求上下文专属的存储容器,每个请求独立隔离,天然适配高并发场景。

步骤1:修改ApiKeyHandler,验证成功后存储JWT

protected override async Task<AuthenticateResult> HandleAuthenticationAsync()
{
    if (Request.Headers.TryGetValue(ApiKeyAuthenticationOptions.ApiKeyHeaderName, out var apiKeyHeaderValues))
    {
        // 原ApiKey校验逻辑保留
        headerKey = apiKeyHeaderValues.ToArray().FirstOrDefault();
        if (!string.IsNullOrEmpty(validKey) && !validKey.Equals(headerKey) && !validKey.Equals(uriKey))
        {
            return AuthenticateResult.NoResult();
        }
    }
    else
    {
        var jwt = Request.Headers["Authorization"].FirstOrDefault(x => x.StartsWith("Bearer "));
        if (string.IsNullOrEmpty(jwt))
            return AuthenticateResult.Fail("No ApiKey or JWT token present in request headers.");

        var tokenHandler = new JwtSecurityTokenHandler();
        var validationParameters = new TokenValidationParameters
        {
            ValidateIssuer = false,
            ValidateAudience = false,
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey))
        };
        try
        {
            var jwtToken = tokenHandler.ReadJwtToken(jwt[7..]);
            var expClaim = jwtToken.Claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Exp)?.Value;
            validationParameters.ValidateLifetime = !string.IsNullOrEmpty(expClaim);

            tokenHandler.ValidateToken(jwt[7..], validationParameters, out SecurityToken validatedToken);
            
            // 将有效JWT存入当前请求上下文
            Context.Items["ValidJwtToken"] = jwt;
        }
        catch
        {
            return AuthenticateResult.NoResult();
        }
    }

    // 补充认证成功的主体创建逻辑(必须返回成功结果才能进入后续管道)
    var claims = new List<Claim>();
    var identity = new ClaimsIdentity(claims, Scheme.Name);
    var principal = new ClaimsPrincipal(identity);
    return AuthenticateResult.Success(new AuthenticationTicket(principal, Scheme.Name));
}

步骤2:修改CustomService,动态获取JWT并添加请求头

重要:禁止在构造函数中设置HttpClient默认头(HttpClient是池化复用对象,默认头会被所有请求共享,并发下必然串用),应在每次发送请求时动态添加:

public class CustomService
{
    private readonly ILogger<CustomService> _logger;
    private readonly string _apiKey;
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly IHttpContextAccessor _httpContextAccessor;

    // 注入IHttpContextAccessor获取当前请求上下文
    public CustomService(ILogger<CustomService> logger, IConfiguration configuration, 
                         IHttpClientFactory httpClientFactory, IHttpContextAccessor httpContextAccessor)
    {
        _logger = logger;
        _apiKey = configuration.GetValue<string>("ApiKey");
        _httpClientFactory = httpClientFactory;
        _httpContextAccessor = httpContextAccessor;
    }

    public async Task<T> SendRequestAsync<T>(string url, HttpMethod method)
    {
        var client = _httpClientFactory.CreateClient();
        var request = new HttpRequestMessage(method, url);

        // 从当前请求上下文提取JWT
        if (_httpContextAccessor.HttpContext?.Items.TryGetValue("ValidJwtToken", out var jwtObj) == true && jwtObj is string jwt)
        {
            request.Headers.Add("Authorization", jwt);
        }
        else
        {
            // 无有效JWT时使用ApiKey
            request.Headers.Add("X-Api-Key", _apiKey);
        }

        var response = await client.SendAsync(request);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadFromJsonAsync<T>();
    }
}

步骤3:注册IHttpContextAccessor

在Program.cs中添加DI注册:

builder.Services.AddHttpContextAccessor();

方案2:将JWT存入ClaimsPrincipal(符合认证规范)

如果JWT包含用户身份信息,可将JWT作为Claim存入认证主体,后续通过HttpContext.User获取。

步骤1:修改ApiKeyHandler,将JWT加入认证Claims

protected override async Task<AuthenticateResult> HandleAuthenticationAsync()
{
    // 原校验逻辑保留...
    try
    {
        // 原验证代码保留...
        tokenHandler.ValidateToken(jwt[7..], validationParameters, out SecurityToken validatedToken);
        
        // 将JWT作为Claim加入认证主体
        var claims = new List<Claim>
        {
            new Claim("ValidJwt", jwt)
            // 可添加其他从JWT解析出的身份Claims
        };
        var identity = new ClaimsIdentity(claims, Scheme.Name);
        var principal = new ClaimsPrincipal(identity);
        return AuthenticateResult.Success(new AuthenticationTicket(principal, Scheme.Name));
    }
    catch
    {
        return AuthenticateResult.NoResult();
    }
}

步骤2:修改CustomService从Claims获取JWT

public async Task<T> SendRequestAsync<T>(string url, HttpMethod method)
{
    var client = _httpClientFactory.CreateClient();
    var request = new HttpRequestMessage(method, url);

    // 从当前用户Claims中提取JWT
    var jwt = _httpContextAccessor.HttpContext?.User.FindFirstValue("ValidJwt");
    if (!string.IsNullOrEmpty(jwt))
    {
        request.Headers.Add("Authorization", jwt);
    }
    else
    {
        request.Headers.Add("X-Api-Key", _apiKey);
    }

    // 后续请求逻辑...
}

关键注意事项

  • 绝对禁止在构造函数中设置HttpClient默认头:HttpClient是池化复用对象,默认头会被所有请求共享,并发场景下必然导致令牌串用
  • 必须使用请求上下文级别的存储:HttpContext.Items或HttpContext.User是每个请求独立的,完全适配高并发
  • IHttpContextAccessor必须注册:只有在DI容器中注册后,才能在非中间件类中获取当前请求上下文

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 21:00:58