Flutter中结合Provider与Dio实现Token刷新的问题求助
Flutter中结合Provider与Dio实现Token刷新的问题求助
各位好,我现在开发Flutter应用时碰到了Token刷新相关的问题,想请大家帮忙排查下。我的项目用Provider做状态管理、Dio处理网络请求,架构是MVVM,用repositories和services作为数据层,路由用go_router,通过它的refreshListenable监听AuthRepository的isAuthenticated getter,当状态变为false时自动跳转到登录页。
先给大家贴出关键代码片段:
AuthRepository实现类
class AuthRepositoryImpl extends AuthRepository { AuthRepositoryImpl({ required AuthApiService authApiService, required GoogleAuthService googleAuthService, required SecureStorageService secureStorageService, }) : _authApiService = authApiService, _googleAuthService = googleAuthService, _secureStorageService = secureStorageService; final AuthApiService _authApiService; final GoogleAuthService _googleAuthService; final SecureStorageService _secureStorageService; bool? _isAuthenticated; @override Future<bool> get isAuthenticated async { // 优先返回缓存的认证状态 if (_isAuthenticated != null) { return _isAuthenticated!; } // 未缓存则从安全存储中获取 final result = await _secureStorageService.getAccessToken(); switch (result) { case Ok<String?>(): _isAuthenticated = result.value != null; case Error<String?>(): _log.severe( 'Failed to fetch access token from secure storage', result.error, ); } return _isAuthenticated ?? false; } // ... 其他未贴出的业务方法 }
MultiProvider依赖配置
List<SingleChildWidget> get providers { return [ Provider(create: (context) => SecureStorageService()), Provider(create: (context) => GoogleAuthService()), Provider( create: (context) => ApiClient(secureStorageService: context.read()), ), Provider(create: (context) => AuthApiService(apiClient: context.read())), ChangeNotifierProvider( create: (context) => AuthRepositoryImpl( authApiService: context.read(), googleAuthService: context.read(), secureStorageService: context.read(), ) as AuthRepository, ), ]; }
ApiClient(Dio封装类)
class ApiClient { ApiClient({required SecureStorageService secureStorageService}) : _secureStorageService = secureStorageService, _client = _instantiateApiClient(), _rawClient = _instantiateApiClient() { _client.interceptors.addAll([ RequestHeadersInterceptor(secureStorageService: _secureStorageService), PrettyDioLogger( requestHeader: true, requestBody: true, responseBody: true, enabled: kReleaseMode == false, ), UnauthorizedInterceptor( mainClient: _client, rawClient: _rawClient, secureStorageService: _secureStorageService, ), ]); } final Dio _client; final Dio _rawClient; final SecureStorageService _secureStorageService; Dio get client => _client; static Dio _instantiateApiClient() { return Dio( BaseOptions( baseUrl: 'https://localhost:5000', headers: {'Content-Type': 'application/json'}, ), ); } }
处理401的UnauthorizedInterceptor
这个拦截器用来实现Token自动刷新,我用了信号量确保只有第一个401请求触发刷新,其余请求等待刷新完成后重试:
class UnauthorizedInterceptor extends Interceptor { UnauthorizedInterceptor({ required Dio mainClient, required Dio rawClient, required SecureStorageService secureStorageService, }) : _rawClient = rawClient, _mainClient = mainClient, _secureStorageService = secureStorageService; final Dio _mainClient; final Dio _rawClient; final SecureStorageService _secureStorageService; // 控制Token刷新的信号量 bool _isRefreshing = false; // 等待刷新完成的Completer Completer<void>? _refreshTokenCompleter; @override void onError(DioException err, ErrorInterceptorHandler handler) async { if (err.response?.statusCode != 401 && !err.requestOptions.path.contains('/auth/refresh')) { return handler.next(err); } if (_isRefreshing) { // 已有刷新请求在进行,等待完成 if (_refreshTokenCompleter != null) { try { await _refreshTokenCompleter!.future; } on Exception { _logoutUser(); return handler.next(err); } } // 重试原请求 return await _repeatRequestAfterTokenRefresh(err, handler); } // 触发Token刷新流程 await _refreshToken(err, handler); } Future<void> _refreshToken( DioException originalError, ErrorInterceptorHandler handler, ) async { _isRefreshing = true; _refreshTokenCompleter = Completer<void>(); try { final refreshTokenResult = await _secureStorageService.getRefreshToken(); if (refreshTokenResult is Error) { return _logoutUser(); } final refreshToken = (refreshTokenResult as Ok<String>).value; final refreshTokenResponse = await _rawClient.post( '/auth/refresh', options: Options(headers: {'Authorization': refreshToken}), ); final data = TokenRefreshResponse.fromJson(refreshTokenResponse.data); // 此处代码未贴完,后续逻辑应为保存新的accessToken和refreshToken到安全存储 // ... } catch (e) { // 刷新失败则执行登出 _logoutUser(); handler.next(originalError); } finally { _isRefreshing = false; _refreshTokenCompleter?.complete(); } } // 省略_repeatRequestAfterTokenRefresh和_logoutUser方法实现 // ... }
现在我遇到的问题:
- Token刷新成功后,AuthRepository的
_isAuthenticated缓存状态没有更新,导致go_router的refreshListenable无法感知到状态变化,状态管理和实际认证状态不同步 - 不确定UnauthorizedInterceptor的逻辑是否有潜在漏洞,比如多个请求等待刷新时的异常处理、刷新失败后的登出逻辑是否严谨
想请教大家:
- 如何让AuthRepository在Token刷新成功/失败后,正确更新
_isAuthenticated缓存,并且通知go_router的refreshListenable状态变化? - 我的UnauthorizedInterceptor逻辑有哪些可以优化的地方?
- 整个Token刷新流程还有哪些需要注意的细节,能让它更健壮?
麻烦各位帮忙分析下,谢谢啦!
内容来源于stack exchange
相关产品推荐
相关产品推荐

