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

Xamarin.Forms+Refit调用API时令牌过期重发仍401问题求助

解决Xamarin.Forms中Refit刷新AccessToken后重发请求仍401的问题

看起来你遇到的核心问题有几个关键点,我帮你梳理下并给出修复方案:

问题分析

  1. HttpRequestMessage不可重复发送:当你第一次调用base.SendAsync(request)后,请求的内容(尤其是Content部分)已经被读取消耗,直接重发原请求会导致无效的请求,这是你重发后依然401的重要原因之一。
  2. Handler的token未实时更新:你的AuthenticatedHttpClientHandler在构造时传入固定的_token,刷新token后这个字段依然是旧值,后续正常请求还是会用旧token(虽然401分支里手动设置了,但逻辑不够严谨)。
  3. 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;
        }
    }
}

额外检查点

  1. 确认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);
    }
    
  2. 检查RefreshAccessToken返回值:调试确认这个方法确实返回了包含有效AccessToken的对象,没有返回null或空值。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:46:52