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,需要调整你的拦截器实现:
- 确保拦截器是单例:避免每次创建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() }
- 关键注意事项:
- 必须排除登录请求被拦截,否则登录失败返回401会无限循环
- 重试成功后要重置计数器,避免后续正常请求受到之前重试次数的影响
- 超过最大重试次数后要停止重试,直接返回原响应
额外提示
- 登录操作最好是同步的(因为拦截器/authenticator是在OkHttp的线程池中执行的),如果你的登录是异步的,需要调整为同步调用
- 确保登录操作能正确获取到有效的新token,否则即使重试也会继续返回401导致循环
内容的提问来源于stack exchange,提问作者TooManyEduardos
相关产品推荐
相关产品推荐

