Flutter基于http包实现Token过期自动刷新与请求重试方案
解决Flutter中使用http包自动处理Token过期刷新并重试请求的问题
因为http包没有内置拦截器机制,我们可以通过封装全局请求方法来实现自动检测401、刷新Token并重试的逻辑。以下是具体实现步骤和代码:
核心思路
- 统一所有请求到一个封装方法里,自动带上当前的access token
- 检查响应状态码,遇到401时触发Token刷新流程
- 处理并发请求场景,避免重复调用refresh token接口
- 刷新成功后自动重试原请求,失败则跳转登录
具体实现代码
1. 定义全局状态与工具类
先创建一个Http工具类,管理Token状态和请求逻辑:
import 'dart:convert'; import 'package:http/http.dart' as http; import 'dart:async'; class HttpAuthClient { static String? _accessToken; static String? _refreshToken; static bool _isRefreshing = false; static Completer<void>? _refreshCompleter; // 初始化Token(比如从SharedPreferences读取) static Future<void> initTokens(String accessToken, String refreshToken) async { _accessToken = accessToken; _refreshToken = refreshToken; // 可选:持久化存储,比如用SharedPreferences // final prefs = await SharedPreferences.getInstance(); // await prefs.setString('access_token', accessToken); // await prefs.setString('refresh_token', refreshToken); } // 封装核心请求方法 static Future<http.Response> sendRequest( String url, { String method = 'GET', Map<String, String>? headers, dynamic body, }) async { // 构建带Token的请求头 final authHeaders = { ...headers ?? {}, if (_accessToken != null) 'Authorization': 'Bearer $_accessToken', }; http.Response response; try { // 执行原请求 response = await _executeRequest(url, method, authHeaders, body); // 检测401状态码 if (response.statusCode == 401) { return _handleTokenRefreshAndRetry(url, method, headers, body); } return response; } catch (e) { rethrow; } } // 执行具体HTTP请求 static Future<http.Response> _executeRequest( String url, String method, Map<String, String> headers, dynamic body, ) async { switch (method.toUpperCase()) { case 'GET': return await http.get(Uri.parse(url), headers: headers); case 'POST': return await http.post( Uri.parse(url), headers: headers, body: body is Map ? jsonEncode(body) : body, encoding: body is Map ? Encoding.getByName('utf-8') : null, ); case 'PUT': return await http.put( Uri.parse(url), headers: headers, body: body is Map ? jsonEncode(body) : body, ); // 按需扩展DELETE等其他方法 default: throw Exception('不支持的HTTP方法'); } } // 处理Token刷新与请求重试 static Future<http.Response> _handleTokenRefreshAndRetry( String url, String method, Map<String, String>? headers, dynamic body, ) async { // 如果正在刷新Token,等待刷新完成再重试 if (_isRefreshing) { await _refreshCompleter!.future; return sendRequest(url, method: method, headers: headers, body: body); } _isRefreshing = true; _refreshCompleter = Completer<void>(); try { // 调用Refresh Token接口 final refreshResponse = await http.post( Uri.parse('https://your-api-domain.com/api/refresh-token'), body: jsonEncode({'refresh_token': _refreshToken}), headers: {'Content-Type': 'application/json'}, ); if (refreshResponse.statusCode == 200) { // 解析并更新新Token final tokenData = jsonDecode(refreshResponse.body); _accessToken = tokenData['access_token']; _refreshToken = tokenData['refresh_token']; // 持久化新Token(可选) // final prefs = await SharedPreferences.getInstance(); // await prefs.setString('access_token', _accessToken!); // await prefs.setString('refresh_token', _refreshToken!); // 通知所有等待的请求刷新完成 _refreshCompleter!.complete(); // 重试原请求 return sendRequest(url, method: method, headers: headers, body: body); } else { // Refresh Token也过期,跳转登录页 // 这里需要根据你的路由方式调整,比如用Navigator // Navigator.of(context).pushReplacementNamed('/login'); throw Exception('Refresh Token失效,请重新登录'); } } catch (e) { _refreshCompleter!.completeError(e); rethrow; } finally { _isRefreshing = false; _refreshCompleter = null; } } }
2. 使用示例
在业务代码中直接调用封装好的请求方法即可:
// 初始化Token(比如登录成功后) await HttpAuthClient.initTokens('your-access-token', 'your-refresh-token'); // 示例:获取用户信息 Future<void> fetchUserInfo() async { try { final response = await HttpAuthClient.sendRequest( 'https://your-api-domain.com/api/user', method: 'GET', ); if (response.statusCode == 200) { final userInfo = jsonDecode(response.body); // 处理用户信息 print(userInfo); } } catch (e) { // 处理错误(比如网络异常、Token刷新失败) print('请求失败: $e'); } }
关键注意事项
- 并发请求处理:通过
_isRefreshing和Completer确保同一时间只有一个刷新请求,其他请求等待刷新完成后重试,避免重复调用刷新接口 - Token持久化:务必将Token存储到本地(如SharedPreferences),否则APP重启后会丢失登录状态
- Refresh Token过期处理:如果刷新接口也返回401,说明Refresh Token已失效,必须引导用户重新登录
- 请求头适配:如果你的API要求特定的Content-Type(比如JSON),要在
_executeRequest中统一处理
内容的提问来源于stack exchange,提问作者Hao Do Van
相关产品推荐
相关产品推荐

