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
相关产品推荐
相关产品推荐

