.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
相关产品推荐
相关产品推荐

