如何在HttpClient中处理Bearer Token过期问题?
自动处理Bearer Token过期的最佳方案
针对你的需求,使用DelegatingHandler扩展HttpClient的请求管道是最优解——它能让你在每次请求发送前自动检查并刷新Token,完全避免重复编写检查逻辑。
步骤1:实现线程安全的Token存储类
用来保存当前Token和过期时间,处理多线程场景下的并发问题:
public class TokenStore { private string _accessToken; private DateTime _expirationTime; private readonly object _lockObj = new object(); // 判断Token是否过期(提前1分钟刷新,避免刚好过期的请求失败) public bool IsTokenExpired() { lock (_lockObj) { return string.IsNullOrEmpty(_accessToken) || DateTime.UtcNow >= _expirationTime; } } public string GetCurrentToken() { lock (_lockObj) { return _accessToken; } } public void UpdateToken(string newToken, int expiresInSeconds) { lock (_lockObj) { _accessToken = newToken; _expirationTime = DateTime.UtcNow.AddSeconds(expiresInSeconds - 60); } } }
步骤2:实现自定义DelegatingHandler
这是核心逻辑,负责在请求前检查Token、刷新Token,以及处理401重试:
public class BearerTokenHandler : DelegatingHandler { private readonly TokenStore _tokenStore; private readonly HttpClient _tokenClient; private readonly string _tokenEndpoint; private readonly string _clientId; private readonly string _clientSecret; public BearerTokenHandler(TokenStore tokenStore, string tokenEndpoint, string clientId, string clientSecret) { _tokenStore = tokenStore; _tokenEndpoint = tokenEndpoint; _clientId = clientId; _clientSecret = clientSecret; // 单独用一个HttpClient获取Token,避免循环引用 _tokenClient = new HttpClient(); } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // 检查Token是否过期,过期则刷新 if (_tokenStore.IsTokenExpired()) { await RefreshToken(cancellationToken); } // 给请求添加Authorization头 request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _tokenStore.GetCurrentToken()); // 发送请求 var response = await base.SendAsync(request, cancellationToken); // 处理401场景:如果检查Token后还是过期了(比如多线程并发),刷新后重试一次 if (response.StatusCode == HttpStatusCode.Unauthorized) { await RefreshToken(cancellationToken); request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _tokenStore.GetCurrentToken()); response = await base.SendAsync(request, cancellationToken); } return response; } // 从服务器获取新Token的方法,根据你的认证方式调整(比如Client Credentials/Password模式) private async Task RefreshToken(CancellationToken cancellationToken) { var formData = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("grant_type", "client_credentials"), new KeyValuePair<string, string>("client_id", _clientId), new KeyValuePair<string, string>("client_secret", _clientSecret) }); var tokenResponse = await _tokenClient.PostAsync(_tokenEndpoint, formData, cancellationToken); tokenResponse.EnsureSuccessStatusCode(); var tokenResult = await tokenResponse.Content.ReadFromJsonAsync<TokenDto>(cancellationToken); _tokenStore.UpdateToken(tokenResult.access_token, tokenResult.expires_in); } // 对应服务器返回的Token结构 private class TokenDto { public string access_token { get; set; } public int expires_in { get; set; } public string token_type { get; set; } } }
步骤3:重构你的HttpClientHelper
把自定义Handler注册到HttpClient中,确保HttpClient单例(避免频繁创建导致的端口耗尽):
public class HttpClientHelper { private static HttpClient _httpClient; private static readonly TokenStore _tokenStore = new TokenStore(); // 替换成你的实际配置 private const string TokenEndpoint = "https://your-auth-server/token"; private const string ClientId = "your-client-id"; private const string ClientSecret = "your-client-secret"; public static HttpClient Request() { if (_httpClient == null) { // 构建Handler链:自定义TokenHandler -> 原生HttpClientHandler var tokenHandler = new BearerTokenHandler(_tokenStore, TokenEndpoint, ClientId, ClientSecret); tokenHandler.InnerHandler = new HttpClientHandler(); _httpClient = new HttpClient(tokenHandler); // 可以设置默认BaseAddress,简化后续请求 // _httpClient.BaseAddress = new Uri("http://myurl.com"); } return _httpClient; } }
步骤4:使用方式
现在发起请求时完全不需要手动处理Token,逻辑自动执行:
var uri = "http://myurl.com/api/your-resource"; using (var request = new HttpRequestMessage(HttpMethod.Get, uri)) { using (var response = await HttpClientHelper.Request().SendAsync(request, new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token)) { if (response.IsSuccessStatusCode) { // 处理你的业务逻辑 } } }
方案优势
- 完全自动化:请求前自动检查、刷新Token,无需重复编写检查逻辑
- 线程安全:Token存储加锁,避免多线程并发冲突
- 容错性强:处理401重试,覆盖Token检查后刚好过期的边缘场景
- 解耦性好:Token处理逻辑与业务请求逻辑完全分离
内容的提问来源于stack exchange,提问作者dbsoft
相关产品推荐
相关产品推荐

