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

Dart代码中Token过期触发401错误,如何实现Token刷新?

实现Token自动刷新方案

1. 调整认证模型与本地存储逻辑

首先得让后端登录接口返回refreshToken(刷新令牌),然后修改你的AuthResponse类新增对应字段,同时在登录时把refreshToken也存到本地:

// 先更新AuthResponse模型
class AuthResponse {
  final String? userId;
  final String? token;
  final String? refreshToken; // 新增刷新令牌字段

  AuthResponse(this.userId, this.token, this.refreshToken);
}

// 修改Login方法,保存refreshToken
Future<AuthResponse?> Login(LoginStructure user) async {
  try {
    var response = await _dio.post("/login",
        data: {"username": user.username, "password": user.password});

    var data = response.data['data'];
    var authResponse = AuthResponse(
      data['userId'],
      data['token'],
      data['refreshToken'], // 从接口获取refreshToken
    );

    if (authResponse.token != null && authResponse.refreshToken != null) {
      await _dataService.AddItem("token", authResponse.token);
      await _dataService.AddItem("refreshToken", authResponse.refreshToken); // 保存到本地
    }

    return authResponse;
  } catch (error) {
    print(error);
    return null;
  }
}

2. 添加Dio拦截器自动处理Token刷新

用Dio的拦截器统一处理请求头和401错误,不用每个接口重复写逻辑:

class UserClient {
  final _dio = Dio(BaseOptions(baseUrl: BaseUrl));
  DataService _dataService = DataService();
  bool _isRefreshing = false; // 防止多请求重复触发刷新
  final _refreshCompleter = Completer<void>();

  UserClient() {
    // 注册拦截器
    _dio.interceptors.add(InterceptorsWrapper(
      // 请求前自动携带Token
      onRequest: (options, handler) async {
        var token = await _dataService.TryGetItem("token");
        if (token != null) {
          options.headers["Authorization"] = "Bearer $token";
        }
        // 统一设置Content-Type和Accept
        options.headers["Content-Type"] = "application/json";
        options.headers["Accept"] = "application/json";
        return handler.next(options);
      },
      // 处理401错误,尝试刷新Token
      onError: (error, handler) async {
        if (error.response?.statusCode == 401 && !_isRefreshing) {
          _isRefreshing = true;
          try {
            var refreshToken = await _dataService.TryGetItem("refreshToken");
            if (refreshToken != null) {
              // 调用后端刷新Token接口
              var refreshResponse = await _dio.post("/refresh-token",
                  data: {"refreshToken": refreshToken});

              var newToken = refreshResponse.data['data']['token'];
              var newRefreshToken = refreshResponse.data['data']['refreshToken'];

              // 更新本地存储的令牌
              await _dataService.AddItem("token", newToken);
              await _dataService.AddItem("refreshToken", newRefreshToken);

              // 重试原请求,替换新Token
              error.requestOptions.headers["Authorization"] = "Bearer $newToken";
              final retryResponse = await _dio.request(
                error.requestOptions.path,
                options: Options(
                  method: error.requestOptions.method,
                  headers: error.requestOptions.headers,
                ),
                data: error.requestOptions.data,
                queryParameters: error.requestOptions.queryParameters,
              );
              // 返回重试后的响应
              return handler.resolve(retryResponse);
            } else {
              // 没有refreshToken,跳转到登录页
              _navigateToLogin();
            }
          } catch (refreshError) {
            // 刷新失败,清除本地凭证并跳转登录
            await _dataService.RemoveItem("token");
            await _dataService.RemoveItem("refreshToken");
            _navigateToLogin();
          } finally {
            _isRefreshing = false;
            if (_refreshCompleter.isCompleted) {
              _refreshCompleter.complete();
            }
          }
        }
        // 非401错误直接抛出
        return handler.next(error);
      },
    ));
  }

  // 跳转登录页的逻辑(根据你的路由框架实现,比如Flutter的Navigator)
  void _navigateToLogin() {
    // 示例:Flutter环境下跳转到登录页
    // Navigator.of(context).pushReplacementNamed('/login');
    print("Token失效,请重新登录");
  }

  // 简化GetUserAsync:拦截器已自动处理请求头
  Future<List<User>?> GetUserAsync() async {
    try {
      var response = await _dio.get("/GetUsers");

      List<User> users = [];
      for (var user in response.data) {
        users.add(User(
          user["_id"],
          user["Username"],
          user["Password"],
          user["Email"],
          user["AuthLevel"],
        ));
      }
      return users;
    } catch (error) {
      print(error);
      return null;
    }
  }

  // 上面修改后的Login方法...
}

3. 关键细节说明

  • _isRefreshing标志:避免多个请求同时触发401时,重复调用刷新接口造成资源浪费。
  • 后端配合:必须有/refresh-token接口,接收refreshToken并返回新的token和refreshToken(部分后端会返回新的refreshToken,按实际情况调整)。
  • 异常兜底:刷新失败时一定要清除本地的token和refreshToken,防止陷入无限循环请求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 19:12:36