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

ASP.NET Core 8 MVC调用外部API:如何在请求间维护访问令牌?

问题解答

一、请求间维护OAuth访问令牌的正确姿势

1. 用户会话存储的利弊

用会话存令牌实现起来简单,且天然和用户上下文绑定,适合每个用户有独立令牌的场景,但要注意几个问题:

  • 默认会话存在服务器内存,多服务器部署时会出现令牌不共享的问题,得改成分布式会话(比如对接Redis)
  • 令牌是敏感数据,会话Cookie必须配置HttpOnly、Secure、SameSite属性,否则容易遭遇XSS或CSRF攻击
  • 如果会话过期时间比令牌有效期短,会提前丢失令牌,导致不必要的令牌请求

2. 更推荐的令牌缓存方案

(1)分布式缓存(多服务器必选)

如果你的应用是多实例部署,用分布式缓存按用户标识(比如用户ID)作为键存储令牌和过期时间是最优解:

// 注入IDistributedCache
private readonly IDistributedCache _cache;

public async Task<string> GetAccessTokenAsync(string userId)
{
    var cacheKey = $"OAuthToken:{userId}";
    var tokenData = await _cache.GetStringAsync(cacheKey);
    
    if (tokenData == null)
    {
        // 调用授权服务器获取新令牌
        var newToken = await RequestNewTokenAsync();
        // 缓存时预留5分钟缓冲,避免令牌刚过期就请求失败
        await _cache.SetStringAsync(cacheKey, newToken, new DistributedCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(55)
        });
        return newToken;
    }
    return tokenData;
}

(2)框架内置的令牌缓存

如果你的应用是用AddOAuth或AddOpenIdConnect配置的外部认证,框架会自动把令牌存在AuthenticationProperties中,直接通过HttpContext.GetTokenAsync就能获取:

// 控制器内获取令牌
var accessToken = await HttpContext.GetTokenAsync("你的OAuth方案名", "access_token");
if (string.IsNullOrEmpty(accessToken))
{
    // 令牌失效,触发重新认证流程
    return Challenge("你的OAuth方案名");
}

这种方式的令牌会加密存储在Cookie中,也可配置为持久化到分布式缓存,无需自行编写缓存逻辑。

3. 令牌刷新要提前处理

令牌有效期1小时,建议在缓存中存储令牌的过期时间,每次获取令牌时检查如果剩余时间不足10分钟,就自动触发刷新,避免请求被拒绝后再处理。

二、IHttpClientFactory + 类型化客户端对令牌维护的影响

改用IHttpClientFactory绝对是正确的选择,不仅能解决手动创建HttpClient导致的socket耗尽问题,还能让令牌维护更省心:

1. 用DelegatingHandler自动附加令牌

编写自定义的DelegatingHandler,在发送请求前自动获取令牌并添加到请求头中,控制器无需再手动处理令牌逻辑:

public class OAuthTokenHandler : DelegatingHandler
{
    private readonly ITokenService _tokenService;
    private readonly IHttpContextAccessor _httpContextAccessor;

    public OAuthTokenHandler(ITokenService tokenService, IHttpContextAccessor httpContextAccessor)
    {
        _tokenService = tokenService;
        _httpContextAccessor = httpContextAccessor;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var userId = _httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        var accessToken = await _tokenService.GetAccessTokenAsync(userId);
        
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
        
        return await base.SendAsync(request, cancellationToken);
    }
}

// 注册类型化客户端时添加该Handler
services.AddHttpClient<IMyExternalApiClient, MyExternalApiClient>()
    .AddHttpMessageHandler<OAuthTokenHandler>();

2. 和令牌缓存逻辑完全解耦

类型化客户端只是封装了HttpClient的创建和配置,你之前考虑的令牌缓存方案(会话、分布式缓存)都能直接使用,完全不冲突,只需将缓存逻辑封装在ITokenService中即可。

3. 统一处理令牌过期重试

在DelegatingHandler中还能捕获401未授权响应,自动刷新令牌并重试请求,无需在每个控制器动作中编写重复的重试逻辑:

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
    var userId = _httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    var accessToken = await _tokenService.GetAccessTokenAsync(userId);
    request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
    
    var response = await base.SendAsync(request, cancellationToken);
    
    if (response.StatusCode == HttpStatusCode.Unauthorized)
    {
        // 刷新令牌
        var newToken = await _tokenService.RefreshTokenAsync(userId);
        // 更新请求头
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", newToken);
        // 重试一次请求
        response = await base.SendAsync(request, cancellationToken);
    }
    
    return response;
}

总结

  • 令牌维护优先选择分布式缓存(多服务器场景)或框架内置的AuthenticationProperties缓存(单服务器场景),使用会话存储时一定要做好安全配置和分布式适配。
  • 改用IHttpClientFactory + 类型化客户端是最佳实践,既能解决HttpClient的资源占用问题,还能通过DelegatingHandler统一处理令牌相关逻辑,让控制器代码更简洁。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 13:53:11