refresh token刷新后重试API请求 修复无效access token报错
问题产生原因
- 重试逻辑未被等待,返回值错误。401分支中用
Future.delayed包裹的重试请求没有加await,属于脱离外层函数生命周期的“悬空异步任务”,外层函数会直接返回第一次请求拿到的401响应,根本不会把重试后的结果返回给调用方。 - 重试请求的认证请求头字段错误。首次请求使用标准的
Authorization字段传递Bearer Token,重试时错误换成了x-access-token字段,后端无法从标准位置读取到新Token,自然判定访问令牌无效。 - 无并发刷新保护。如果同时有多个接口触发401,会重复调用Token刷新接口,部分后端实现中刷新Token仅可使用一次,重复调用会导致刚拿到的新Refresh Token失效,后续所有请求全部认证失败。
- 异常分支缺失。Token刷新接口调用失败(比如Refresh Token本身过期)时没有做处理,会继续带着无效Token发起重试,必然返回认证错误。
可行修复方案
核心修复思路是修正异步逻辑写法、统一请求头、增加并发锁、补全异常处理,修复后的完整代码如下:
import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:http/io_client.dart'; // 省略你原有ApiConstants等业务导入 class ApiService { String? accessToken; String? refreshToken; bool status = false; final http.Client _ioClient = IOClient(); // 新增并发锁标记,防止重复刷新Token bool _isRefreshing = false; Future<http.Response> _getAPIById(String pathURL) async { try { final Uri uri = Uri.http(ApiConstants.baseUrl, pathURL); // 统一构造认证请求头,避免字段写错 Map<String, String> _buildAuthHeaders() { return { 'Content-Type': 'application/json', 'Authorization': 'Bearer $accessToken' }; } // 首次发起请求 http.Response response = await _ioClient.get( uri, headers: _buildAuthHeaders(), ); // 触发Token刷新逻辑:仅当返回401时执行 if (response.statusCode == 401) { // 无正在进行的刷新任务时,才发起刷新请求 if (!_isRefreshing) { _isRefreshing = true; try { await refreshAccessToken(); } catch (_) { _isRefreshing = false; rethrow; } _isRefreshing = false; } else { // 已有刷新任务在执行,等待刷新完成 while (_isRefreshing) { await Future.delayed(const Duration(milliseconds: 50)); } } // 刷新完成后用新Token重试原请求,直接覆盖原响应对象 response = await _ioClient.get( uri, headers: _buildAuthHeaders(), ); } // 统一处理响应日志和返回 if (response.statusCode == 200) { print("请求成功"); print(response.body); } else { print("请求异常,状态码:${response.statusCode}"); print(response.body); } return response; } catch (e) { print("请求出错:$e"); throw Exception('接口请求失败'); } } Future<String> refreshAccessToken() async { if (refreshToken == null) { throw Exception('登录凭证已失效'); } Map<String, String> requestBody = {'refresh_token': refreshToken!}; final response = await http.post( Uri.parse(ApiConstants.baseUrlWithHttp + ApiConstants.refreshAccessToken), body: requestBody, headers: { "Accept": "application/json", "Content-Type": "application/x-www-form-urlencoded", }, encoding: Encoding.getByName("utf-8"), ); print("执行Token刷新"); print(response.body); if (response.statusCode == 200) { final decodedData = jsonDecode(response.body); accessToken = decodedData['accessToken']; // 后端返回新Refresh Token时才更新,避免覆盖有效值 if (decodedData['refreshToken'] != null) { refreshToken = decodedData['refreshToken']; } status = true; return response.body; } else { // 刷新失败,清空本地登录状态,供上层跳转登录页 accessToken = null; refreshToken = null; status = false; throw Exception('登录状态已过期,请重新登录'); } } }
核心改动说明
- 移除了无等待的
Future.delayed逻辑,Token刷新完成后立刻发起重试,重试结果直接覆盖原响应对象统一返回,不会再返回第一次的401错误响应。 - 抽离统一的请求头构造方法,所有请求固定使用标准
Authorization字段传递Token,修复了之前重试时请求头字段写错的问题。 - 新增
_isRefreshing并发锁,多接口同时触发401时仅执行一次刷新操作,其余请求等待刷新完成后再重试,避免重复刷新导致Token失效。 - 补全全链路异常处理:Refresh Token无效、刷新接口调用失败时,会清空本地登录态并抛出明确错误,方便上层做重新登录的跳转逻辑,不会继续发起无效重试。
- 简化冗余的状态码判断逻辑,首次请求和重试请求的响应统一处理,避免分支漏洞。
内容的提问来源于stack exchange,提问作者Naveen Kumar
相关产品推荐
相关产品推荐

