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

如何在Flutter中实现JWT token刷新?所需包有哪些?仅用HTTP包可以实现吗?

Flutter JWT Token刷新功能实现方案

核心实现流程

JWT 鉴权体系默认返回短期有效的access_token(用来发起鉴权请求)和长期有效的refresh_token(用来刷新过期的access_token),完整刷新逻辑如下:

  • 首次登录成功后,将接口返回的access_token、refresh_token、access_token过期时间三个字段持久化存储到本地
  • 所有需要鉴权的业务接口,请求头中添加Authorization: Bearer ${access_token}字段
  • 接口返回401未授权状态码,或者本地判断access_token即将过期时,触发刷新逻辑:
    1. 加锁标记当前正在刷新token,避免多个并发请求同时触发重复刷新
    2. 携带本地存储的refresh_token调用刷新接口,获取新的access_token和refresh_token
    3. 更新本地存储的三个字段,清空请求队列里缓存的待重试请求,依次重新发起
    4. 如果刷新接口返回失败,说明refresh_token也已过期,清空本地存储的用户信息,跳转登录页让用户重新登录

相关问题解答

1、实现该功能需要使用哪些依赖包?

  • 核心依赖:仅需http(网络请求)和Dart内置的dart:convert(JSON解析)即可完成基础功能
  • 辅助依赖:shared_preferences用来持久化存储token信息,避免每次APP重启都要重新登录;如果用dio网络库可以直接通过拦截器实现自动刷新逻辑,不用手动封装每个请求,开发效率更高

2、仅通过HTTP包是否可以完成该功能的开发?

可以。http包已经提供了完整的网络请求能力,你只需要手动封装通用请求方法,在方法内加入token过期判断、刷新逻辑、重试逻辑即可,不需要额外引入其他网络库。

现有代码优化示例

你当前的代码是首次登录获取token的逻辑,优化后如下,包含token存储和通用鉴权请求封装(需额外导入dart:async使用Completer):

import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:async';
import 'package:shared_preferences/shared_preferences.dart';

// 标记是否正在刷新token,避免并发重复刷新
bool _isRefreshing = false;
// 请求队列,缓存刷新过程中收到的请求
List<Function> _requestQueue = [];

// 首次登录获取token
Future<void> getAuth(String username, String password) async {
  final response = await http.post(Uri.parse("你的登录接口地址"), body: {
    'username': username,
    'password': password,
    'grant_type': "password",
    'client_id': "2LJhXdj2Cu0LlVqHk2ilm1WWHtdwK**********",
    'client_secret': "buQIh6tLPC0hG6QZUMm7yP7QuSjBLiffTBx3zY2HYxw95ssXQ4F85ttE5fGHInm1LBkk9GhGiHZCvoR21l4bqIOQTTJBo0nJ5******************"
  });

  if (response.statusCode == 200) {
    Map<String, dynamic> responseMap = json.decode(response.body);
    String accessToken = responseMap['access_token'];
    String refreshToken = responseMap['refresh_token'];
    // 接口一般会返回access_token的有效时长,单位为秒
    int expiresIn = responseMap['expires_in'];
    DateTime expireTime = DateTime.now().add(Duration(seconds: expiresIn));
    
    // 持久化存储到本地
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('access_token', accessToken);
    await prefs.setString('refresh_token', refreshToken);
    await prefs.setString('expire_time', expireTime.toIso8601String());
  }
}

// 封装通用鉴权请求,自带自动刷新token逻辑
Future<http.Response> sendAuthRequest(String url, {String method = 'GET', Map<String, dynamic>? body}) async {
  final prefs = await SharedPreferences.getInstance();
  String? accessToken = prefs.getString('access_token');
  String? refreshToken = prefs.getString('refresh_token');
  String? expireTimeStr = prefs.getString('expire_time');

  // 无有效登录信息,抛出异常引导跳转登录
  if (accessToken == null || refreshToken == null || expireTimeStr == null) {
    throw Exception('未登录');
  }

  DateTime expireTime = DateTime.parse(expireTimeStr);
  // 提前1分钟判断token即将过期,主动刷新避免请求失败
  if (DateTime.now().isAfter(expireTime.subtract(const Duration(minutes: 1)))) {
    if (!_isRefreshing) {
      _isRefreshing = true;
      try {
        // 调用刷新token接口
        final refreshResponse = await http.post(Uri.parse("你的刷新token接口地址"), body: {
          'grant_type': 'refresh_token',
          'refresh_token': refreshToken,
          'client_id': "你的client_id",
          'client_secret': "你的client_secret"
        });

        if (refreshResponse.statusCode == 200) {
          Map<String, dynamic> refreshData = json.decode(refreshResponse.body);
          // 更新本地存储的token信息
          accessToken = refreshData['access_token'];
          refreshToken = refreshData['refresh_token'];
          int newExpiresIn = refreshData['expires_in'];
          DateTime newExpireTime = DateTime.now().add(Duration(seconds: newExpiresIn));
          await prefs.setString('access_token', accessToken!);
          await prefs.setString('refresh_token', refreshToken!);
          await prefs.setString('expire_time', newExpireTime.toIso8601String());

          // 执行队列中缓存的所有请求
          for (var req in _requestQueue) {
            req();
          }
          _requestQueue.clear();
        } else {
          // refresh_token也过期,清空本地数据跳转登录
          await prefs.clear();
          throw Exception('登录已过期,请重新登录');
        }
      } finally {
        _isRefreshing = false;
      }
    } else {
      // 正在刷新中,把当前请求加入队列等待刷新完成后执行
      Completer<http.Response> completer = Completer();
      _requestQueue.add(() async {
        completer.complete(await sendAuthRequest(url, method: method, body: body));
      });
      return completer.future;
    }
  }

  // 构造请求头发起实际请求
  Map<String, String> headers = {
    'Authorization': 'Bearer $accessToken',
    'Content-Type': 'application/x-www-form-urlencoded'
  };

  http.Response response;
  if (method.toUpperCase() == 'POST') {
    response = await http.post(Uri.parse(url), headers: headers, body: body);
  } else {
    response = await http.get(Uri.parse(url), headers: headers);
  }

  // 兜底判断,如果还是返回401,触发刷新逻辑
  if (response.statusCode == 401) {
    return sendAuthRequest(url, method: method, body: body);
  }

  return response;
}

内容的提问来源于stack exchange,提问作者Alan Bosco

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 21:00:03