使用YARP时,代理请求返回401如何刷新Access Token并重试请求
在YARP中处理401并自动刷新Token并重试请求
YARP官方没有直接封装这类场景的现成方案,但可以通过自定义响应处理逻辑结合请求重试来实现需求。核心思路是拦截代理返回的401响应,触发Token刷新流程,更新凭证后重新发起请求。
实现步骤与代码示例
修改你的YARP服务配置,添加响应处理逻辑,同时保留原有请求带Token的逻辑:
builder.Services.AddReverseProxy() .LoadFromConfig(builder.Configuration.GetRequiredSection("ReverseProxy")) .AddTransforms(builder => { // 原有逻辑:为代理请求添加Bearer Token builder.AddRequestTransform(async (ctx) => { var accessToken = await ctx.HttpContext.GetTokenAsync("access_token"); if (!string.IsNullOrEmpty(accessToken)) { ctx.ProxyRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); } }); // 新增响应处理:拦截401并尝试刷新Token重试 builder.AddResponseTransform(async (ctx) => { // 仅在首次收到401且未重试时执行逻辑 if (ctx.ProxyResponse.StatusCode == HttpStatusCode.Unauthorized && !ctx.HttpContext.Items.ContainsKey("ProxyRequestRetried")) { ctx.HttpContext.Items["ProxyRequestRetried"] = true; var refreshToken = await ctx.HttpContext.GetTokenAsync("refresh_token"); // 无Refresh Token则直接返回401 if (string.IsNullOrEmpty(refreshToken)) return; // 调用认证服务刷新Token var newTokens = await FetchNewTokens(refreshToken); if (newTokens == null || string.IsNullOrEmpty(newTokens.AccessToken)) return; // 更新认证Cookie中的Token(适配Cookie存储场景) var authProps = new AuthenticationProperties(); authProps.StoreTokens(new[] { new AuthenticationToken { Name = "access_token", Value = newTokens.AccessToken }, new AuthenticationToken { Name = "refresh_token", Value = newTokens.RefreshToken } }); await ctx.HttpContext.SignInAsync(ctx.HttpContext.User.Identity.Name, ctx.HttpContext.User, authProps); // 克隆原请求并替换为新Token var retryRequest = ctx.ProxyRequest.Clone(); retryRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", newTokens.AccessToken); // 发起重试请求并替换原响应 var forwarder = ctx.HttpContext.RequestServices.GetRequiredService<IHttpForwarder>(); var retryResult = await forwarder.SendAsync( ctx.HttpContext, ctx.DestinationPrefix, retryRequest, ctx.HttpContext.RequestAborted, new ForwarderRequestConfig { ActivityTimeout = TimeSpan.FromSeconds(10) }); ctx.ProxyResponse = retryResult.Response; } }); }); // 刷新Token的具体实现,需替换为你的认证服务接口逻辑 private async Task<TokenPair> FetchNewTokens(string refreshToken) { using var httpClient = new HttpClient(); var tokenRequest = new FormUrlEncodedContent(new Dictionary<string, string> { { "grant_type", "refresh_token" }, { "refresh_token", refreshToken }, { "client_id", "你的客户端ID" }, { "client_secret", "你的客户端密钥" } }); var response = await httpClient.PostAsync("https://你的认证服务器地址/token", tokenRequest); return response.IsSuccessStatusCode ? await response.Content.ReadFromJsonAsync<TokenPair>() : null; } // 定义Token返回模型,根据认证服务返回结构调整 public class TokenPair { [JsonPropertyName("access_token")] public string AccessToken { get; set; } [JsonPropertyName("refresh_token")] public string RefreshToken { get; set; } }
关键注意事项
- 避免无限重试:通过
HttpContext.Items标记已重试,防止刷新Token失败后循环触发401处理逻辑。 - Token存储适配:如果你的Token不是存在Cookie中,需要修改获取和更新Token的逻辑(比如从内存缓存、Session等获取)。
- 认证服务适配:
FetchNewTokens方法需要完全匹配你的认证服务的请求参数、请求方式和返回格式。 - 超时与异常处理:可以根据实际业务需求添加重试超时设置、异常捕获逻辑,避免阻塞请求。
内容的提问来源于stack exchange,提问作者Szyszka947
相关产品推荐
相关产品推荐

