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被吊销、网络异常),会自动触发登出,保证用户状态的一致性
- 全项目兼容性:因为你全项目依赖
tokengetter,而我们只是更新了_token变量并触发notifyListeners(),所以现有代码不需要任何修改就能自动使用新的token
这样改造后,用户只要登录一次,就能一直保持登录状态,除非主动登出或账号出现问题。
内容的提问来源于stack exchange,提问作者Masooma Suleman
相关产品推荐
相关产品推荐

