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

OkHttp令牌拦截器遇401无限循环重试问题求助

解决OkHttp拦截器401重试无限循环及计数器重置问题

看起来你遇到的问题主要来自两个核心点:计数器作用域错误导致每次重置,以及未排除登录请求被拦截引发循环,甚至可能是用错了拦截器类型(普通Interceptor不如Authenticator适合处理认证重试场景)。下面一步步帮你解决:

问题根源分析

  • 计数器重置问题:如果你的计数器是定义在intercept()方法内部的局部变量,每次拦截请求时都会重新初始化,自然无法累计重试次数;如果拦截器是每次创建OkHttpClient时都new一个新实例(比如你代码里addInterceptor(NetworkInterceptor())),那成员变量的计数器也会每次重置。
  • 无限循环问题:当你拦截到401后发起登录请求,如果这个登录请求也被同一个拦截器拦截,而登录请求本身又返回401(比如登录凭证无效),就会触发无限重试;另外,如果你重试原请求时没有标记,会再次被拦截,重复执行401处理逻辑。

解决方案

方案1:使用OkHttp官方推荐的Authenticator(最稳妥)

OkHttp专门提供了Authenticator接口来处理认证失败的场景,它会自动帮你控制重试逻辑,避免不必要的循环,而且不需要自己维护计数器:

object Service {
    private fun getOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
            .addInterceptor(getLoggingInterceptor())
            .authenticator(object : Authenticator {
                private var retryCount = 0
                private val MAX_RETRY = 2 // 限制最大重试次数

                override fun authenticate(route: Route?, response: Response): Request? {
                    // 超过重试次数就放弃
                    if (retryCount >= MAX_RETRY) {
                        return null
                    }
                    retryCount++

                    // 排除登录请求,避免循环
                    val requestUrl = response.request.url.toString()
                    if (requestUrl.contains("/login")) {
                        return null // 登录请求本身失败,直接返回null不重试
                    }

                    // 执行登录操作,获取新的token
                    val newToken = performLogin() // 这里替换成你的登录逻辑

                    // 用新token重新构建请求
                    return response.request.newBuilder()
                        .header("Authorization", "Bearer $newToken")
                        .build()
                }
            })
            .build()
    }

    // 你的其他Retrofit构建代码...
}

方案2:修复自定义Interceptor的问题(如果你坚持用Interceptor)

如果你不想改用Authenticator,需要调整你的拦截器实现:

  1. 确保拦截器是单例:避免每次创建OkHttpClient都new新的拦截器实例,这样计数器才能保持状态:
// 把NetworkInterceptor改成单例
object NetworkInterceptor : Interceptor {
    private var retryCount = 0
    private val MAX_RETRY = 2

    override fun intercept(chain: Interceptor.Chain): Response {
        var request = chain.request()
        var response = chain.proceed(request)

        // 排除登录请求,避免循环触发
        if (request.url.toString().contains("/login")) {
            return response
        }

        if (response.code == 401 && retryCount < MAX_RETRY) {
            retryCount++
            // 执行登录获取新token
            val newToken = performLogin()
            // 重新构建带新token的请求
            request = request.newBuilder()
                .header("Authorization", "Bearer $newToken")
                .build()
            // 重试请求
            response = chain.proceed(request)
            // 重试成功后重置计数器,不影响后续正常请求
            if (response.isSuccessful) {
                retryCount = 0
            }
        } else {
            // 非401或超过重试次数,重置计数器
            retryCount = 0
        }
        return response
    }
}

// 在OkHttpClient中使用单例拦截器
private fun getOkHttpClient(): OkHttpClient {
    return OkHttpClient.Builder()
        .addInterceptor(getLoggingInterceptor())
        .addInterceptor(NetworkInterceptor) // 用单例,而非new实例
        .build()
}
  1. 关键注意事项:
    • 必须排除登录请求被拦截,否则登录失败返回401会无限循环
    • 重试成功后要重置计数器,避免后续正常请求受到之前重试次数的影响
    • 超过最大重试次数后要停止重试,直接返回原响应

额外提示

  • 登录操作最好是同步的(因为拦截器/authenticator是在OkHttp的线程池中执行的),如果你的登录是异步的,需要调整为同步调用
  • 确保登录操作能正确获取到有效的新token,否则即使重试也会继续返回401导致循环

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 03:23:53