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

YARP认证:如何实现令牌刷新?附场景与代码示例

YARP代理中Service1端令牌刷新的最佳实现方式

你的代码问题分析

你当前的实现会导致502错误,核心原因是:

  • 错误使用了未定义的_next变量,应该用传入的next委托
  • 第一次调用next后,HttpContext的请求/响应流已被消耗、状态已被修改,无法直接重复调用next重发请求,这会导致代理无法正确处理二次请求,返回网关错误

最佳实现方式:自定义代理中间件+令牌缓存服务

推荐通过封装令牌刷新逻辑的独立服务+自定义代理中间件实现,核心思路是:先尝试用有效令牌发送请求,收到401时刷新令牌、重置请求上下文并重发,同时缓存有效令牌避免重复刷新。


1. 封装令牌管理服务

先实现一个负责令牌获取、刷新、缓存的服务,避免并发场景下重复调用刷新接口:

public interface ITokenService
{
    Task<string> GetValidTokenAsync();
    Task<string> RefreshTokenAsync();
}

public class TokenService : ITokenService
{
    private readonly HttpClient _httpClient;
    private string _currentToken;
    private DateTime _tokenExpiry;
    private readonly SemaphoreSlim _refreshLock = new SemaphoreSlim(1, 1);

    public TokenService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<string> GetValidTokenAsync()
    {
        await _refreshLock.WaitAsync();
        try
        {
            // 令牌为空或即将过期时刷新
            if (string.IsNullOrEmpty(_currentToken) || DateTime.UtcNow >= _tokenExpiry)
            {
                _currentToken = await RefreshTokenAsync();
                // 解析JWT令牌的过期时间,提前1分钟刷新避免过期
                var jwtHandler = new JwtSecurityTokenHandler();
                var jwtToken = jwtHandler.ReadJwtToken(_currentToken);
                _tokenExpiry = jwtToken.ValidTo.AddMinutes(-1);
            }
            return _currentToken;
        }
        finally
        {
            _refreshLock.Release();
        }
    }

    public async Task<string> RefreshTokenAsync()
    {
        // 调用Service1的令牌刷新接口,根据实际业务调整请求参数
        var refreshResponse = await _httpClient.PostAsync("/api/auth/refresh", null);
        refreshResponse.EnsureSuccessStatusCode();
        
        var tokenResult = await refreshResponse.Content.ReadFromJsonAsync<TokenResponse>();
        return tokenResult.AccessToken;
    }
}

public class TokenResponse
{
    public string AccessToken { get; set; }
}

2. 正确实现代理中间件

在YARP代理管道中添加自定义中间件,处理401时的令牌刷新与请求重发:

endpoints.MapReverseProxy(proxyPipeline =>
{
    proxyPipeline.Use(async (context, next) =>
    {
        var tokenService = context.RequestServices.GetRequiredService<ITokenService>();
        // 首次请求前设置有效令牌
        var initialToken = await tokenService.GetValidTokenAsync();
        context.Request.Headers.Authorization = $"Bearer {initialToken}";

        // 保存原始请求体(请求流只能读取一次,重发时需要重置)
        var originalRequestBody = await ReadRequestBody(context.Request);

        // 第一次发送请求
        await next(context);

        // 收到401时尝试刷新令牌并重发
        if (context.Response.StatusCode == StatusCodes.Status401Unauthorized)
        {
            // 刷新令牌
            var newToken = await tokenService.RefreshTokenAsync();
            
            // 重置响应上下文(清空已写入的响应内容)
            context.Response.Clear();
            // 更新请求头的令牌
            context.Request.Headers.Authorization = $"Bearer {newToken}";
            
            // 重置请求体流
            if (originalRequestBody != null)
            {
                context.Request.Body = new MemoryStream(originalRequestBody);
                context.Request.ContentLength = originalRequestBody.Length;
            }
            
            // 重新执行代理管道,发送更新令牌后的请求
            await next(context);
        }
    });
    
    proxyPipeline.UseSessionAffinity();
    proxyPipeline.UseLoadBalancing();
    proxyPipeline.UsePassiveHealthChecks();
});

// 辅助方法:读取并保存请求体内容
private async Task<byte[]> ReadRequestBody(HttpRequest request)
{
    if (!request.ContentLength.HasValue || request.ContentLength == 0)
        return null;
    
    using var memoryStream = new MemoryStream();
    await request.Body.CopyToAsync(memoryStream);
    memoryStream.Position = 0;
    request.Body = memoryStream;
    return memoryStream.ToArray();
}

3. 关键注意事项

  • 用SemaphoreSlim控制令牌刷新的并发,避免多个请求同时触发刷新接口
  • 必须保存并重置请求体流,ASP.NET的请求流是单次读取的,重发请求时需要重新提供流内容
  • 收到401后要先清空响应上下文,否则会导致响应内容混乱
  • 令牌过期时间建议提前1分钟刷新,避免网络延迟导致令牌在请求过程中过期

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 20:20:36