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

Flutter基于Firebase Auth实现Token自动刷新以保持用户永久登录

解决方案:基于现有Auth类实现Firebase Token自动刷新

嘿,我刚好做过类似的需求,咱们直接基于你现有的代码来改,核心就是利用Firebase返回的refreshToken在token过期前自动获取新的idToken,实现持久登录。下面一步步来:

核心思路

Firebase邮箱/密码登录时,除了idToken和expiresIn,还会返回一个refreshToken——这个令牌可以用来获取新的idToken,而且有效期比idToken长得多(甚至可以永久有效,除非用户主动登出或账号被禁用)。咱们要做的就是:

  • 存储refreshToken到SharedPreferences
  • 在当前idToken过期前,调用Firebase的刷新接口获取新的idToken和过期时间
  • 更新本地状态和存储,循环这个过程
  • 只有当refreshToken也失效时,才触发登出

修改步骤&完整代码

1. 扩展Auth类的变量

首先添加_refreshToken变量,用来存储刷新令牌:

class Auth with ChangeNotifier{
  String _token;
  DateTime _expiryDate;
  String _userId;
  Timer _authTimer;
  // 新增:存储refreshToken
  String _refreshToken;
  
  // 原有get方法保持不变
  bool get isAuth{
    return token!=null;
  }

  String get token{
    if(_expiryDate!=null&&_expiryDate.isAfter(DateTime.now())&& _token!=null){
      return _token;
    }
    return null;
  }

  String get userId {
    return _userId;
  }
}

2. 改造_authenticate方法,存储refreshToken

在登录/注册成功后,把返回的refreshToken存起来,同时写入SharedPreferences:

Future<void> _authenticate(String email, String password, String urlSegment ) async {
  final url='https://identitytoolkit.googleapis.com/v1/$urlSegment?key=YOUR_API_KEY'; // 替换成你的Firebase Web API密钥
  try{
    final response= await http.post(url, 
      body: json.encode({
        'email': email,
        'password': password,
        'returnSecureToken': true,
      }),
    );
    final responseData= json.decode(response.body);
    if(responseData['error']!=null){
      throw HttpException(responseData['error']['message']);
    }
    _token= responseData['idToken'];
    _userId=responseData['localId'];
    _expiryDate=DateTime.now().add(Duration (seconds: int.parse( responseData['expiresIn']) ) );
    // 新增:存储refreshToken
    _refreshToken = responseData['refreshToken'];
    // 替换原来的_autoLogout为自动刷新逻辑
    _autoRefreshToken();
    notifyListeners();
    final prefs=await SharedPreferences.getInstance();
    final userData= json.encode({
      'token': _token,
      'userId': _userId,
      'expiryDate': _expiryDate.toIso8601String(),
      // 新增:保存refreshToken到本地
      'refreshToken': _refreshToken,
    });
    prefs.setString('userData', userData);
  } catch (error){
    throw(error);
  }
}

3. 实现自动刷新逻辑

替换原来的_autoLogout为_autoRefreshToken,提前30秒触发刷新(避免刚好过期时请求失败):

void _autoRefreshToken(){
  if(_authTimer!=null){
    _authTimer.cancel();
  }
  // 提前30秒刷新,避免刚好过期时请求失败
  final timeToExpiry= _expiryDate.difference(DateTime.now()).inSeconds - 30;
  // 如果剩余时间小于等于0,立即刷新
  final triggerTime = timeToExpiry > 0 ? timeToExpiry : 0;
  _authTimer= Timer(Duration(seconds: triggerTime), _refreshTokenAndUpdate);
}

// 新增:调用Firebase刷新接口的方法
Future<void> _refreshTokenAndUpdate() async {
  final url = 'https://securetoken.googleapis.com/v1/token?key=YOUR_API_KEY'; // 替换成你的Firebase Web API密钥
  try {
    final response = await http.post(
      Uri.parse(url),
      body: json.encode({
        'grant_type': 'refresh_token',
        'refresh_token': _refreshToken,
      }),
    );
    final responseData = json.decode(response.body);
    if (responseData['error'] != null) {
      // 刷新失败(比如refreshToken过期),触发登出
      await logout();
      return;
    }
    // 更新token和过期时间
    _token = responseData['id_token'];
    _expiryDate = DateTime.now().add(
      Duration(seconds: int.parse(responseData['expires_in'])),
    );
    // 更新refreshToken(Firebase可能会返回新的refreshToken,建议保存)
    _refreshToken = responseData['refresh_token'];
    // 通知监听器,全项目自动使用新token
    notifyListeners();
    // 保存到本地存储
    final prefs = await SharedPreferences.getInstance();
    final userData = json.encode({
      'token': _token,
      'userId': _userId,
      'expiryDate': _expiryDate.toIso8601String(),
      'refreshToken': _refreshToken,
    });
    prefs.setString('userData', userData);
    // 启动下一次刷新定时器
    _autoRefreshToken();
  } catch (error) {
    // 网络错误等异常,触发登出
    await logout();
  }
}

4. 修改tryAutoLogin方法,恢复refreshToken

当APP重启时,不仅恢复token和过期时间,还要恢复refreshToken,并启动刷新定时器:

Future<bool> tryAutoLogin() async{
  final prefs= await SharedPreferences.getInstance();
  if(!prefs.containsKey('userData')){
    return false;
  }
  final extractedUserData= json.decode(prefs.getString('userData')) as Map<String, Object>;
  final expiryDate= DateTime.parse(extractedUserData['expiryDate']);
  
  _refreshToken = extractedUserData['refreshToken'] as String;
  _userId = extractedUserData['userId'] as String;

  if(expiryDate.isBefore(DateTime.now())) {
    // 如果已过期,尝试直接刷新token
    await _refreshTokenAndUpdate();
    return isAuth;
  }
  
  _token=extractedUserData['token'] as String;
  _expiryDate=expiryDate;
  notifyListeners();
  _autoRefreshToken(); // 替换原来的_autoLogout
  return true;
}

5. 调整logout方法

确保登出时清空refreshToken:

Future<void> logout() async{
  _token=null;
  _userId=null;
  _expiryDate=null;
  _refreshToken=null; // 新增:清空refreshToken
  if(_authTimer!=null){
    _authTimer.cancel();
    _authTimer=null;
  }
  notifyListeners();
  final prefs= await SharedPreferences.getInstance();
  prefs.remove('userData');
}

关键注意事项

  • 替换API_KEY:所有接口URL里的YOUR_API_KEY要换成你Firebase项目的Web API密钥
  • 提前刷新时间:设置提前30秒刷新是为了避免在token刚好过期的瞬间发起请求导致失败,你可以根据自己的需求调整这个时间
  • 错误处理:如果刷新失败(比如refreshToken被吊销、网络异常),会自动触发登出,保证用户状态的一致性
  • 全项目兼容性:因为你全项目依赖token getter,而我们只是更新了_token变量并触发notifyListeners(),所以现有代码不需要任何修改就能自动使用新的token

这样改造后,用户只要登录一次,就能一直保持登录状态,除非主动登出或账号出现问题。

内容的提问来源于stack exchange,提问作者Masooma Suleman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 13:27:52