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

如何避免Flutter Dio包拦截器在Token刷新时陷入无限循环

Dio拦截器实现Token刷新避免无限循环的解决方案

问题根源

你遇到的无限循环,核心原因有两个:

  • 重新发起请求时没有替换成新的Token,导致新请求依然携带失效Token,再次触发401错误,重复进入拦截器的错误分支。
  • 未做并发请求的防重复刷新处理,多个请求同时触发401时,会重复调用refreshToken,甚至引发多次循环。

修复步骤及代码示例

1. 刷新Token后更新请求头

在获取到新Token后,必须更新原请求的headers(或者全局Dio的默认headers),确保重新发起的请求携带有效Token。

2. 增加刷新锁,避免重复刷新

用一个布尔变量标记是否正在刷新Token,防止多个并发请求同时触发刷新逻辑。

3. 完善错误分支逻辑

当refreshToken失败时,要直接终止循环,比如清空本地Token并跳转登录页,避免无效重复请求。

修改后的完整代码:

class DioFetch {
  Dio dio = Dio();
  // 刷新锁:标记是否正在刷新Token
  bool _isRefreshing = false;

  DioFetch() {
    demoInceptor();
  }

  void demoInceptor() {
    dio.interceptors.clear();
    dio.interceptors.add(
      InterceptorsWrapper(
        onRequest: (options, handler) {
          print("onRequest");
          // 从本地获取Token并添加到请求头
          final token = getLocalToken();
          if (token != null) {
            options.headers['Authorization'] = 'Bearer $token';
          }
          return handler.next(options);
        },
        onError: (DioError e, handler) async {
          if (e.response?.statusCode == 401) {
            print("statuscode: 401");
            // 避免重复刷新
            if (_isRefreshing) {
              return handler.reject(e);
            }

            _isRefreshing = true;
            try {
              final refreshResp = await refreshToken();
              if (refreshResp.statusCode == 200) {
                final newToken = refreshResp.data['token'];
                // 保存新Token到本地
                saveLocalToken(newToken);
                // 更新当前请求的Token
                e.requestOptions.headers['Authorization'] = 'Bearer $newToken';
                
                // 重新发起请求
                final resp = await dio.request(
                  e.requestOptions.path,
                  data: e.requestOptions.data,
                  cancelToken: e.requestOptions.cancelToken,
                  onReceiveProgress: e.requestOptions.onReceiveProgress,
                  onSendProgress: e.requestOptions.onSendProgress,
                  queryParameters: e.requestOptions.queryParameters,
                  options: Options(
                    method: e.requestOptions.method,
                    headers: e.requestOptions.headers,
                  ),
                );
                return handler.resolve(resp);
              } else {
                // 刷新Token失败,清空本地Token并跳转登录
                clearLocalToken();
                // 这里可以添加跳转登录页的逻辑,比如Navigator.pushReplacement
                return handler.reject(e);
              }
            } catch (refreshError) {
              // 刷新过程中出现异常,同样清空Token并跳转
              clearLocalToken();
              return handler.reject(refreshError as DioError);
            } finally {
              // 刷新结束,释放锁
              _isRefreshing = false;
            }
          }
          // 非401错误,直接传递错误
          return handler.next(e);
        },
      ),
    );
  }

  // 模拟从本地获取Token
  String? getLocalToken() {
    // 实际项目中用SharedPreferences或其他存储
    return null;
  }

  // 模拟保存新Token到本地
  void saveLocalToken(String token) {
    // 实际项目中用SharedPreferences或其他存储
  }

  // 模拟清空本地Token
  void clearLocalToken() {
    // 实际项目中用SharedPreferences或其他存储
  }

  Future<Response> refreshToken() async {
    // 这里写实际的刷新Token逻辑,比如调用接口获取新Token
    return dio.post('/refresh-token', data: {'refresh_token': getLocalRefreshToken()});
  }

  // 模拟获取本地的refreshToken
  String? getLocalRefreshToken() {
    // 实际项目中从存储获取
    return null;
  }
}

关键细节说明

  • 刷新锁_isRefreshing:确保同一时间只有一个请求触发Token刷新,避免重复调用refreshToken接口。
  • 更新请求头:重新发起请求前,必须把新Token写入请求头,否则新请求依然会返回401,导致循环。
  • 失败处理:刷新Token失败时,一定要清空本地Token并引导用户重新登录,彻底终止循环。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 10:57:52