.NET Core中为Typed HttpClient实现认证头复用与Token自动刷新方案咨询
最佳实现方案:用自定义DelegatingHandler封装认证逻辑
我之前也碰到过完全一样的场景,最优雅的解决方式是借助.NET Core HttpClient的DelegatingHandler,把认证相关的逻辑(获取token、缓存token、403时刷新重试)完全封装到请求管道里,让你的业务服务类彻底不用关心认证细节,只专注于调用外部API的业务逻辑。
下面是具体的实现步骤,代码都是可直接复用的:
1. 实现线程安全的Token缓存存储类
我们需要一个地方缓存authToken和refreshToken,还要保证多线程并发调用时的安全性(毕竟HttpClient是多线程用的):
public class ExternalApiTokenStore { private readonly HttpClient _httpClient; private string _authToken; private string _refreshToken; private DateTime _tokenExpiry; private readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1); private readonly IConfiguration _configuration; public ExternalApiTokenStore(HttpClient httpClient, IConfiguration configuration) { _httpClient = httpClient; _configuration = configuration; } public async Task<string> GetValidTokenAsync() { await _lock.WaitAsync(); try { // 检查token是否有效(留1分钟缓冲,避免刚好过期的情况) if (!string.IsNullOrEmpty(_authToken) && _tokenExpiry > DateTime.UtcNow.AddMinutes(1)) { return _authToken; } // 优先尝试用refreshToken刷新,失败就重新登录 if (!string.IsNullOrEmpty(_refreshToken)) { try { var newTokens = await RefreshTokenAsync(); UpdateTokens(newTokens); return _authToken; } catch { // 刷新失败,清空refreshToken,走登录流程 _refreshToken = null; } } // 重新登录获取新token var loginTokens = await LoginAsync(); UpdateTokens(loginTokens); return _authToken; } finally { _lock.Release(); } } private void UpdateTokens(TokenResponse tokens) { _authToken = tokens.AuthToken; _refreshToken = tokens.RefreshToken; _tokenExpiry = DateTime.UtcNow.AddSeconds(tokens.ExpiresIn); } private async Task<TokenResponse> LoginAsync() { // 替换成外部API的登录请求逻辑,比如POST JSON/表单 var loginPayload = new { Username = _configuration["ExternalApi:Username"], Password = _configuration["ExternalApi:Password"] }; var response = await _httpClient.PostAsJsonAsync(_configuration["ExternalApi:LoginEndpoint"], loginPayload); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync<TokenResponse>(); } private async Task<TokenResponse> RefreshTokenAsync() { // 替换成外部API的刷新token请求逻辑 var refreshPayload = new { RefreshToken = _refreshToken }; var response = await _httpClient.PostAsJsonAsync(_configuration["ExternalApi:RefreshEndpoint"], refreshPayload); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsync<TokenResponse>(); } // 外部API返回的token模型,根据实际结构调整 private class TokenResponse { public string AuthToken { get; set; } public string RefreshToken { get; set; } public int ExpiresIn { get; set; } } }
2. 实现自定义DelegatingHandler
这个Handler会自动给每个请求加认证头,遇到403时自动刷新token并重试一次:
public class ExternalApiAuthHandler : DelegatingHandler { private readonly ExternalApiTokenStore _tokenStore; public ExternalApiAuthHandler(ExternalApiTokenStore tokenStore) { _tokenStore = tokenStore; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // 获取有效token,自动处理登录/刷新 var authToken = await _tokenStore.GetValidTokenAsync(); // 给请求加认证头(这里假设用Bearer认证,根据外部API调整) request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken); // 发送请求 var response = await base.SendAsync(request, cancellationToken); // 收到403时,强制刷新token并重试一次 if (response.StatusCode == HttpStatusCode.Forbidden) { // 重新获取新token var newAuthToken = await _tokenStore.GetValidTokenAsync(); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", newAuthToken); // 重试请求 response = await base.SendAsync(request, cancellationToken); } return response; } }
3. 注册服务和HttpClient
在Program.cs里把这些组件注册到DI容器,注意要避免循环依赖:
// 先注册TokenStore,给它单独的HttpClient(避免和业务HttpClient循环依赖) builder.Services.AddHttpClient<ExternalApiTokenStore>() .ConfigureHttpClient(client => { client.BaseAddress = new Uri(builder.Configuration["ExternalApi:BaseUrl"]); }); // 注册自定义认证Handler builder.Services.AddTransient<ExternalApiAuthHandler>(); // 注册你的Typed HttpClient,并把认证Handler加入请求管道 builder.Services.AddHttpClient<IExternalService, ExternalService>() .ConfigureHttpClient(client => { client.BaseAddress = new Uri(builder.Configuration["ExternalApi:BaseUrl"]); }) .AddHttpMessageHandler<ExternalApiAuthHandler>();
4. 简化你的业务服务类
现在你的ExternalService完全不用管认证,直接调用HttpClient就行:
public class ExternalService : IExternalService { private readonly HttpClient _httpClient; public ExternalService(HttpClient httpClient) { _httpClient = httpClient; } public async Task<YourDataModel> GetDataByIdAsync(int id) { // 直接调用API,认证头由Handler自动添加 return await _httpClient.GetFromJsonAsync<YourDataModel>($"api/resources/{id}"); } // 其他业务方法同理,完全不用处理认证逻辑 }
核心优势
- 彻底解耦:认证逻辑和业务逻辑完全分离,业务代码只关注业务需求
- 自动化处理:所有请求自动加认证头,403时自动刷新重试,无需业务代码干预
- 线程安全:用SemaphoreSlim保证多线程下只有一个请求去刷新token,避免重复登录
- 可复用性:如果有多个服务调用该外部API,都可以复用这套认证逻辑
内容的提问来源于stack exchange,提问作者Szymon Sus
相关产品推荐
相关产品推荐

