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

Dio拦截器刷新Access Token:handler处理与请求重试疑问

Dio拦截器Token刷新问题修复方案

核心问题分析

你的代码能成功刷新Token,但重试请求时没有携带新的Access Token,且未处理Token刷新失败的异常场景,同时handler的异步处理逻辑存在遗漏,导致流程无法正常收尾。

修正后的完整代码

class AuthInterceptor extends QueuedInterceptor {
  final Dio dio;
  final AuthService authService;

  AuthInterceptor(this.dio, this.authService);

  @override
  void onRequest(
      RequestOptions options, RequestInterceptorHandler handler) async {
    final accessToken = await TokenRepository.getAccessToken();

    if (accessToken != null) {
      logDebug('Access-Token: $accessToken');
      options.headers['Authorization'] = 'Bearer $accessToken';
    }
    handler.next(options);
  }

  @override
  void onError(DioError err, ErrorInterceptorHandler handler) async {
    // 仅处理401响应错误
    if (err.type == DioErrorType.response && err.response?.statusCode == 401) {
      final currentAccessToken = await TokenRepository.getAccessToken();
      // 如果当前没有Token,直接跳转登录页
      if (currentAccessToken == null) {
        di<NavigationHelper>().navigateClearHistory(LoginPage());
        return handler.reject(err);
      }

      try {
        final refreshToken = await TokenRepository.getRefreshToken();
        if (refreshToken == null) {
          // 无可用刷新Token,跳转登录
          di<NavigationHelper>().navigateClearHistory(LoginPage());
          return handler.reject(err);
        }

        // 发起Token刷新请求
        final refreshResult = await authService.refresh(
          RefreshRequest(currentAccessToken, refreshToken),
        );
        final newAccessToken = refreshResult.accessToken;
        await TokenRepository.setAccessToken(newAccessToken);

        // 更新原请求的Authorization头为新Token
        err.requestOptions.headers['Authorization'] = 'Bearer $newAccessToken';
        // 重试请求并返回结果
        final retryResponse = await _retry(err.requestOptions);
        handler.resolve(retryResponse);
      } catch (refreshError) {
        // 刷新Token失败,跳转登录页
        di<NavigationHelper>().navigateClearHistory(LoginPage());
        // 将刷新失败的错误传递给上层
        handler.reject(DioError(requestOptions: err.requestOptions, error: refreshError));
      }
    } else {
      // 非401错误,正常传递错误信息
      handler.next(err);
    }
  }

  Future<Response<dynamic>> _retry(RequestOptions requestOptions) async {
    return dio.request<dynamic>(
      requestOptions.path,
      data: requestOptions.data,
      queryParameters: requestOptions.queryParameters,
      options: Options(
        method: requestOptions.method,
        headers: requestOptions.headers, // 已携带新Token的请求头
      ),
    );
  }
}

关键修正点

  • 更新重试请求的Token:刷新Token后直接修改原请求requestOptions的Authorization头,确保重试请求携带有效新Token。
  • 异常场景处理:用try-catch包裹Token刷新逻辑,覆盖刷新失败、刷新Token为空等异常情况,保证流程能正确收尾。
  • handler调用完整性:每个分支都明确调用handler.reject或handler.resolve,避免出现拦截器流程挂起的情况。
  • 空值安全校验:增加refreshToken的空值判断,避免空指针异常。

额外优化建议

  • 增加并发请求锁:如果多个请求同时触发401,可通过全局Completer控制同一时间仅发起一次刷新请求,避免重复调用刷新接口。
  • 提前预判Token过期:在onRequest阶段检查Token是否即将过期,主动发起刷新,减少401重试的场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 08:21:57