如何避免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
相关产品推荐
相关产品推荐

