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

Flutter中如何实现JWT Token过期时自动无感刷新

Flutter 静默刷新JWT Token无感知实现方案

要实现用户无感知的后台Token自动刷新,核心是把所有鉴权、刷新逻辑收敛到网络请求统一拦截层,完全不侵入业务代码,用户操作全程不会被打断,具体实现方案如下:

核心设计原则

  • 业务层不需要感知Token过期、刷新逻辑,正常调用接口即可
  • 并发请求同时触发Token过期时,仅发起1次刷新请求,避免重复调用刷新接口把RefreshToken刷作废
  • 刷新过程中所有在途请求自动挂起,拿到新Token后批量重试,用户操作、页面跳转完全不受影响
  • 仅当RefreshToken也失效、无法完成续期时,才触发登录跳转兜底逻辑

具体实现(基于Dio网络库)

Dio是Flutter生态最常用的网络库,自带拦截器机制可以低成本实现整套逻辑:

  1. 前置准备
    • 用flutter_secure_storage做本地持久化,加密存储accessToken、refreshToken,不要用普通明文存储存敏感凭证
    • 给全局Dio实例配置请求、错误拦截器,统一处理请求头Token注入、过期错误拦截
  2. 拦截器核心代码
import 'package:dio/dio.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class AuthInterceptor extends Interceptor {
  final Dio dio;
  final FlutterSecureStorage _storage = const FlutterSecureStorage();
  // 刷新锁,避免并发重复发起刷新请求
  bool _isRefreshing = false;
  // 挂起队列:存刷新期间触发的待重试请求
  final List<Future<void> Function()> _pendingRequests = [];

  AuthInterceptor(this.dio);

  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
    // 跳过标记为不需要鉴权的请求(比如刷新Token接口、登录接口)
    if (options.extra['skipAuth'] == true) {
      return handler.next(options);
    }
    // 统一注入请求头Token
    final accessToken = await _storage.read(key: 'accessToken');
    if (accessToken != null) {
      options.headers['Authorization'] = 'Bearer $accessToken';
    }
    handler.next(options);
  }

  @override
  void onError(DioError err, ErrorInterceptorHandler handler) async {
    // 和后端对齐Token过期的判断规则,一般是401状态码,部分后端用自定义业务码
    final isTokenExpired = err.response?.statusCode == 401;
    if (!isTokenExpired) return handler.next(err);

    // 不在刷新状态,触发刷新流程
    if (!_isRefreshing) {
      _isRefreshing = true;
      try {
        final refreshToken = await _storage.read(key: 'refreshToken');
        // 刷新接口标记skipAuth,避免死循环
        final refreshRes = await dio.post('/auth/refresh',
            data: {'refreshToken': refreshToken},
            options: Options(extra: {'skipAuth': true}));
        // 存储新的Token对
        final newAccessToken = refreshRes.data['accessToken'];
        final newRefreshToken = refreshRes.data['refreshToken'];
        await _storage.write(key: 'accessToken', value: newAccessToken);
        await _storage.write(key: 'refreshToken', value: newRefreshToken);

        // 批量重试所有挂起的请求
        for (final pendingReq in _pendingRequests) {
          pendingReq();
        }
        _pendingRequests.clear();

        // 重试当前触发401的请求,返回结果给业务层
        final retryRes = await _retryRequest(err.requestOptions);
        return handler.resolve(retryRes);
      } catch (e) {
        // 刷新失败:一般是RefreshToken过期、账号被顶登,清空本地凭证触发登录跳转
        _pendingRequests.clear();
        await _storage.deleteAll();
        // 用全局事件总线发过期事件,在根路由层监听处理跳转,不要在拦截器里直接拿context跳页
        // eventBus.fire(TokenInvalidEvent());
        return handler.next(err);
      } finally {
        _isRefreshing = false;
      }
    }

    // 正在刷新中,把当前请求加入挂起队列
    final completer = Completer<Response>();
    _pendingRequests.add(() async {
      final res = await _retryRequest(err.requestOptions);
      completer.complete(res);
    });
    handler.resolve(await completer.future);
  }

  // 用新Token重试原请求
  Future<Response> _retryRequest(RequestOptions originalReq) async {
    final newToken = await _storage.read(key: 'accessToken');
    return dio.request(
      originalReq.path,
      data: originalReq.data,
      queryParameters: originalReq.queryParameters,
      options: Options(
        method: originalReq.method,
        headers: {...originalReq.headers, 'Authorization': 'Bearer $newToken'},
        extra: originalReq.extra,
      ),
    );
  }
}

避坑说明

  • 不要做「Token快过期提前刷新」的逻辑,本地设备时间和服务端时间很容易出现偏差,提前刷新的稳定性远不如接口返回过期后再触发刷新
  • 刷新全程不要弹全局Loading、Toast提示,保证流程完全静默,用户感知不到后台的刷新操作
  • 大部分后端的RefreshToken是一次性的,并发发多个刷新请求会直接把Token作废,加锁逻辑不能省
  • 如果用其他网络库,逻辑完全通用:统一请求入口、加锁防并发刷新、挂起请求重试、失效兜底跳登录即可

内容的提问来源于stack exchange,提问作者Rozan Shrestha

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 08:24:12