You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter基于http包实现Token过期自动刷新与请求重试方案

解决Flutter中使用http包自动处理Token过期刷新并重试请求的问题

因为http包没有内置拦截器机制,我们可以通过封装全局请求方法来实现自动检测401、刷新Token并重试的逻辑。以下是具体实现步骤和代码:

核心思路

  1. 统一所有请求到一个封装方法里,自动带上当前的access token
  2. 检查响应状态码,遇到401时触发Token刷新流程
  3. 处理并发请求场景,避免重复调用refresh token接口
  4. 刷新成功后自动重试原请求,失败则跳转登录

具体实现代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 18:34:59