Xamarin.Forms+Refit调用API时令牌过期重发仍401问题求助
解决Xamarin.Forms中Refit刷新AccessToken后重发请求仍401的问题
看起来你遇到的核心问题有几个关键点,我帮你梳理下并给出修复方案:
问题分析
- HttpRequestMessage不可重复发送:当你第一次调用
base.SendAsync(request)后,请求的内容(尤其是Content部分)已经被读取消耗,直接重发原请求会导致无效的请求,这是你重发后依然401的重要原因之一。 - Handler的token未实时更新:你的
AuthenticatedHttpClientHandler在构造时传入固定的_token,刷新token后这个字段依然是旧值,后续正常请求还是会用旧token(虽然401分支里手动设置了,但逻辑不够严谨)。 - Token缓存更新的可靠性:需要确认
TokenHelper.CacheToken(newToken)是否真的把新的AccessToken写入了Settings.AccessToken。
修复后的代码实现
我修改了你的AuthenticatedHttpClientHandler,解决了上述问题:
public class AuthenticatedHttpClientHandler : HttpClientHandler { protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // 每次请求都获取最新的AccessToken,避免使用构造时的旧值 var currentAccessToken = Settings.AccessToken; if (!string.IsNullOrWhiteSpace(currentAccessToken)) { request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", currentAccessToken); } else { request.Headers.Remove("Authorization"); } var initialResponse = await base.SendAsync(request, cancellationToken).ConfigureAwait(false); // 处理401过期逻辑 if (initialResponse.StatusCode == HttpStatusCode.Unauthorized && !string.IsNullOrWhiteSpace(Settings.RefreshToken)) { // 获取新token var idsrvApiService = new IdSrvApiService(); var refreshTokenService = new RefreshTokenService(idsrvApiService); var newToken = await refreshTokenService.RefreshAccessToken(Priority.Background).ConfigureAwait(false); // 确认新token有效再重发 if (newToken != null && !string.IsNullOrWhiteSpace(newToken.AccessToken)) { TokenHelper.CacheToken(newToken); // 克隆原始请求,因为原请求内容已被消耗,无法重复发送 var clonedRequest = await CloneHttpRequest(request); clonedRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", newToken.AccessToken); // 发送克隆后的请求 return await base.SendAsync(clonedRequest, cancellationToken).ConfigureAwait(false); } } return initialResponse; } // 克隆HttpRequestMessage的核心方法 private async Task<HttpRequestMessage> CloneHttpRequest(HttpRequestMessage originalRequest) { var clonedRequest = new HttpRequestMessage(originalRequest.Method, originalRequest.RequestUri) { Version = originalRequest.Version }; // 复制所有请求头 foreach (var header in originalRequest.Headers) { clonedRequest.Headers.TryAddWithoutValidation(header.Key, header.Value); } // 复制请求内容(处理不同类型的HttpContent) if (originalRequest.Content != null) { clonedRequest.Content = await CloneHttpContent(originalRequest.Content); foreach (var header in originalRequest.Content.Headers) { clonedRequest.Content.Headers.TryAddWithoutValidation(header.Key, header.Value); } } return clonedRequest; } // 克隆HttpContent的辅助方法,支持常见的内容类型 private async Task<HttpContent> CloneHttpContent(HttpContent originalContent) { if (originalContent is StringContent stringContent) { var contentStr = await stringContent.ReadAsStringAsync().ConfigureAwait(false); return new StringContent(contentStr, stringContent.Headers.ContentType?.Encoding, stringContent.Headers.ContentType?.MediaType); } else if (originalContent is FormUrlEncodedContent formContent) { var formData = await formContent.ReadAsFormDataAsync().ConfigureAwait(false); return new FormUrlEncodedContent(formData); } else if (originalContent is StreamContent streamContent) { var ms = new MemoryStream(); await streamContent.CopyToAsync(ms).ConfigureAwait(false); ms.Position = 0; return new StreamContent(ms); } // 如果你的项目用了MultipartFormDataContent,可以在这里添加对应的克隆逻辑 else { return null; } } }
额外检查点
- 确认Token缓存逻辑:确保
TokenHelper.CacheToken正确更新Settings的值,比如:public static void CacheToken(TokenResponse newToken) { Settings.AccessToken = newToken.AccessToken; Settings.RefreshToken = newToken.RefreshToken; // 建议同时缓存过期时间,后续可以提前刷新,避免401 Settings.TokenExpiresAt = DateTime.Now.AddSeconds(newToken.ExpiresIn); } - 检查RefreshAccessToken返回值:调试确认这个方法确实返回了包含有效AccessToken的对象,没有返回null或空值。
- Refit客户端创建方式:确保你使用修改后的Handler创建HttpClient:
var httpClient = new HttpClient(new AuthenticatedHttpClientHandler()) { BaseAddress = new Uri("https://your-api-domain.com/") }; var yourApi = RestService.For<IYourApiInterface>(httpClient);
内容的提问来源于stack exchange,提问作者Libin Joseph
相关产品推荐
相关产品推荐

