Dio实现Token刷新时偶发401「无效刷新令牌」错误排查求助
Dio实现Token刷新时偶发401「无效刷新令牌」错误排查求助
大家好,我在Flutter项目中基于Dio的QueuedInterceptor实现了Token自动刷新的逻辑——为了防止主Dio实例的拦截器给刷新Token的接口带上过期的accessToken,特意单独创建了_tokenDio实例处理刷新请求。
大部分情况下逻辑都能正常工作:Token过期时自动刷新,并发请求会被队列化,刷新成功后重试所有pending请求。但偶尔会出现401「Invalid Refresh Token」错误,我打印了当时的refreshToken看起来是有效的,但就是刷新失败,实在找不到问题出在哪,想请大家帮忙看看代码有没有疏漏。
我的拦截器代码如下:
class AuthInterceptor extends QueuedInterceptor { final Dio _dio; final FlutterSecureStorage _secureStorage; bool _isRefreshing = false; final List<RequestOptions> _pendingRequests = []; final Dio _tokenDio = Dio( BaseOptions( baseUrl: baseURL, headers: { 'accept': 'application/json', 'Content-Type': 'application/json', }, ), )..interceptors.add( LogInterceptor( request: true, requestHeader: true, requestBody: true, responseBody: true, ), ); AuthInterceptor(this._dio, this._secureStorage); @override void onRequest(RequestOptions options, RequestInterceptorHandler handler) async { final accessToken = await _secureStorage.read(key: 'accessToken'); final refreshToken = await _secureStorage.read(key: 'refreshToken'); // 确保用的是accessToken而非refreshToken if (accessToken != null) { options.headers['Authorization'] = 'Bearer $accessToken'; print('✅ Authorization header set with accessToken'); } else { print('❌ Cannot set Authorization header - accessToken is null'); } super.onRequest(options, handler); } @override void onError(DioException err, ErrorInterceptorHandler handler) async { if (err.response?.statusCode == 401) { // Token过期,尝试刷新 if (_isRefreshing) { // 正在刷新时,将请求加入队列 _pendingRequests.add(err.requestOptions); return; } final refreshToken = await _secureStorage.read(key: 'refreshToken'); if (refreshToken != null) { print("The current refresh token is : $refreshToken"); _isRefreshing = true; try { final newTokens = await _refreshAccessToken(refreshToken); if (newTokens != null) { await _secureStorage.write( key: 'accessToken', value: newTokens['accessToken'], ); await _secureStorage.write( key: 'refreshToken', value: newTokens['refreshToken'], ); // 重试原始请求 final requestOptions = err.requestOptions; requestOptions.headers['Authorization'] = 'Bearer ${newTokens['accessToken']}'; final response = await _dio.fetch(requestOptions); handler.resolve(response); // 处理所有队列中的请求 await _processPendingRequests(newTokens['accessToken']!); return; } } on DioException catch (e) { print('🔴 Token refresh failed (DioException): $e'); await _logOut(); handler.reject(err); return; } catch (e) { print('🔴 Token refresh failed: $e'); await _logOut(); } finally { _isRefreshing = false; } } await _logOut(); // 无有效refreshToken或刷新失败 handler.reject(err); } else { super.onError(err, handler); } } Future<Map<String, String>?> _refreshAccessToken(String refreshToken) async { final response = await _tokenDio .get( RefreshToken, options: Options(headers: {'Authorization': 'Bearer $refreshToken'}), ) .timeout(Duration(seconds: 20)); final data = response.data as Map<String, dynamic>; print('✅ Token refreshed successfully'); print('🔑 New accessToken: ${data['accessToken']}'); print('♻️ New refreshToken: ${data['refreshToken']}'); return { 'accessToken': data['accessToken'] as String, 'refreshToken': data['refreshToken'] as String, }; } Future<void> _processPendingRequests(String newAccessToken) async { print('🔄 Processing ${_pendingRequests.length} pending requests...'); for (final requestOptions in _pendingRequests) { requestOptions.headers['Authorization'] = 'Bearer $newAccessToken'; await _dio.fetch(requestOptions); } _pendingRequests.clear(); } Future<void> _logOut() async { await AuthService.logout(); _pendingRequests.clear(); _isRefreshing = false; } }
已完成的排查:
- 确认刷新Token请求用了独立的
_tokenDio实例,不会被主拦截器处理,不会携带过期accessToken - 触发刷新时会打印当前refreshToken,看起来是有效的,但偶尔仍返回「Invalid Refresh Token」
- 用
_isRefreshing标记和_pendingRequests队列处理并发,避免重复触发刷新 - 刷新成功后会更新本地Token并重试所有pending请求
我怀疑的几个方向(但没找到证据):
- 调用
_refreshAccessToken过程中,本地refreshToken会不会被其他操作(比如用户手动退出)修改? - 并发场景下,
_isRefreshing的锁有没有可能失效,导致多个请求同时调用刷新接口? - 新refreshToken有没有可能没正确写入
FlutterSecureStorage? - refreshToken本身已过期,但代码没处理这种情况,直接调用刷新接口导致401?
有没有大佬能帮我看看代码里的逻辑漏洞,或者推荐一些排查方向?谢谢大家!😥




