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

.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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 13:32:50