Flutter登录认证:如何实现刷新Token及全局传递API Key与Token
解决方案
一、完善登录函数,持久化存储凭证
你的登录接口返回应包含api_key、access_token、refresh_token以及expires_in(Token有效期,单位秒)。我们用shared_preferences做本地持久化,确保APP重启后仍能保留凭证。
先在pubspec.yaml添加依赖:
dependencies: shared_preferences: ^2.2.2 # 可替换为最新版本
修改后的登录函数:
import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; import 'dart:convert'; Future<bool> login(String username, String password) async { final client = http.Client(); try { final response = await client.post( Uri.parse('https://flutter.noviindus.co.in/api/LoginApi'), body: {'username': username, 'password': password}, ); if (response.statusCode == 200) { final data = jsonDecode(response.body); // 按实际返回字段调整key名 final apiKey = data['api_key']; final accessToken = data['access']; final refreshToken = data['refresh']; final expiresIn = data['expires_in']; // 计算Token过期时间戳(当前时间+有效期毫秒数) final expiryTime = DateTime.now().millisecondsSinceEpoch + (expiresIn * 1000); // 写入本地存储 final prefs = await SharedPreferences.getInstance(); await prefs.setString('api_key', apiKey); await prefs.setString('access_token', accessToken); await prefs.setString('refresh_token', refreshToken); await prefs.setInt('token_expiry', expiryTime); return true; } else { print('登录失败:${response.body}'); return false; } } catch (e) { print('请求异常:$e'); return false; } finally { client.close(); } }
二、封装HTTP客户端,自动处理Token刷新与Header注入
手动给每个请求加Header太繁琐,还容易遗漏Token过期处理。封装一个通用客户端,自动完成凭证校验、Token刷新、Header注入:
import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; import 'dart:convert'; class ApiClient { final http.Client _client = http.Client(); static const baseUrl = 'https://flutter.noviindus.co.in/api'; // 获取本地存储的登录凭证 Future<Map<String, dynamic>?> _getCredentials() async { final prefs = await SharedPreferences.getInstance(); final apiKey = prefs.getString('api_key'); final accessToken = prefs.getString('access_token'); final refreshToken = prefs.getString('refresh_token'); final expiryTime = prefs.getInt('token_expiry'); if (apiKey == null || accessToken == null || refreshToken == null || expiryTime == null) { return null; } return { 'api_key': apiKey, 'access_token': accessToken, 'refresh_token': refreshToken, 'is_expired': DateTime.now().millisecondsSinceEpoch > expiryTime, }; } // 调用刷新Token接口 Future<bool> _refreshToken() async { final prefs = await SharedPreferences.getInstance(); final refreshToken = prefs.getString('refresh_token'); if (refreshToken == null) return false; try { final response = await _client.post( Uri.parse('$baseUrl/RefreshTokenApi'), // 替换为你的刷新接口地址 body: {'refresh': refreshToken}, ); if (response.statusCode == 200) { final data = jsonDecode(response.body); final newAccessToken = data['access']; final newExpiresIn = data['expires_in']; final newExpiryTime = DateTime.now().millisecondsSinceEpoch + (newExpiresIn * 1000); await prefs.setString('access_token', newAccessToken); await prefs.setInt('token_expiry', newExpiryTime); return true; } else { // 刷新失败,清除本地凭证,引导重新登录 await prefs.clear(); return false; } } catch (e) { print('刷新Token异常:$e'); return false; } } // 通用请求方法,支持GET/POST/PUT/DELETE Future<http.Response> request( String method, String path, { Map<String, dynamic>? body, Map<String, String>? headers, }) async { var credentials = await _getCredentials(); if (credentials == null) { throw Exception('未登录,请先登录'); } // Token过期则先刷新 if (credentials['is_expired']) { final refreshSuccess = await _refreshToken(); if (!refreshSuccess) { throw Exception('Token过期且刷新失败,请重新登录'); } // 刷新后重新获取凭证 credentials = await _getCredentials(); if (credentials == null) { throw Exception('登录状态失效,请重新登录'); } } // 构建通用请求头 final defaultHeaders = { 'X-API-Key': credentials['api_key'], // 按后端要求调整Header字段名 'Authorization': 'Bearer ${credentials['access_token']}', 'Content-Type': 'application/json', ...?headers, // 合并自定义Header }; final url = Uri.parse('$baseUrl/$path'); http.Response response; switch (method.toUpperCase()) { case 'GET': response = await _client.get(url, headers: defaultHeaders); break; case 'POST': response = await _client.post( url, headers: defaultHeaders, body: body != null ? jsonEncode(body) : null, ); break; case 'PUT': response = await _client.put( url, headers: defaultHeaders, body: body != null ? jsonEncode(body) : null, ); break; case 'DELETE': response = await _client.delete(url, headers: defaultHeaders); break; default: throw Exception('不支持的请求方法'); } // 若返回401,尝试再次刷新Token并重试请求 if (response.statusCode == 401) { final refreshSuccess = await _refreshToken(); if (refreshSuccess) { credentials = await _getCredentials(); if (credentials != null) { final retryHeaders = { 'X-API-Key': credentials['api_key'], 'Authorization': 'Bearer ${credentials['access_token']}', 'Content-Type': 'application/json', ...?headers, }; // 重试原请求 switch (method.toUpperCase()) { case 'GET': response = await _client.get(url, headers: retryHeaders); break; case 'POST': response = await _client.post( url, headers: retryHeaders, body: body != null ? jsonEncode(body) : null, ); break; case 'PUT': response = await _client.put( url, headers: retryHeaders, body: body != null ? jsonEncode(body) : null, ); break; case 'DELETE': response = await _client.delete(url, headers: retryHeaders); break; } } } } return response; } }
三、使用封装后的客户端发起请求
之后所有API请求直接调用ApiClient,无需手动处理Header和Token:
// 示例:获取用户信息 Future<void> getUserInfo() async { final apiClient = ApiClient(); try { final response = await apiClient.request('GET', 'UserInfo'); if (response.statusCode == 200) { final data = jsonDecode(response.body); print('用户信息:$data'); } else { print('获取用户信息失败:${response.body}'); } } catch (e) { print('请求异常:$e'); // 此处可跳转至登录页面 } }
关于Header传递的补充说明
你之前在Header里传Token的思路是对的:
access_token通常放在Authorization头,格式为Bearer <token>- API Key一般放在自定义Header(如
X-API-Key),具体字段名需和后端确认
额外注意点:
- 建议给刷新Token逻辑加锁(比如用
mutex包),避免并发请求重复刷新 - Token过期时间要准确计算,提前判断减少无效请求
- 刷新Token失败时,务必清除本地凭证,避免残留无效数据
内容的提问来源于stack exchange,提问作者Midhun Ap
相关产品推荐
相关产品推荐

