Flutter存储Keycloak返回的Access Token时出现Future实例异常
问题
使用OpenID对接Keycloak获取Access Token后,控制台能正常打印Token内容,但将Token存入Flutter Secure Storage时,发现保存的是Instance of 'Future<String?>'而非实际Token值。尝试添加await后问题仍未解决,相关代码如下:
// Auth类代码 import 'package:doctor/controller/secure_storage.dart'; import 'package:doctor/controller/shared_prefrence.dart'; import 'package:flutter/material.dart'; import 'package:logger/logger.dart'; import 'package:openid_client/openid_client_io.dart'; import 'package:rflutter_alert/rflutter_alert.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher_string.dart'; import '../../Utils/constants.dart'; import '../../view/doctor/doc_profile.dart'; class Auth with ChangeNotifier { final bool _isLogin = UserSecureStorage.readBool("isAuth") == true ? true : false; bool _isLoading = false; void setIsLOading(bool val) { _isLoading = val; notifyListeners(); } bool get isLoading => _isLoading; void setIsLOgin(bool val) { _isLoading = val; notifyListeners(); } var logoutUrl; bool get isAuth => _isLogin; Future authenticate(context) async { setIsLOading(true); var uri = Uri.parse('removed'); var clientId = 'removed'; var clientSecrect = 'removed'; var scopes = List<String>.of([]); var issuer = await Issuer.discover(uri); var client = Client(issuer, clientId, clientSecret: clientSecrect); urlLauncher(String url) async { if (await canLaunchUrlString(url)) { await launchUrlString(url, mode: LaunchMode.inAppWebView, webViewConfiguration: const WebViewConfiguration( enableJavaScript: true, enableDomStorage: false)); } else { throw 'Could not launch $url'; } } try { var authenticator = Authenticator( client, port: 3000, scopes: scopes, redirectUri: null, urlLancher: urlLauncher, ); var c = await authenticator.authorize(); closeInAppWebView(); var token = await c.getTokenResponse(); var user = await c.getUserInfo(); logoutUrl = c.generateLogoutUrl(); var idtoken = token.idToken; var acesstoken = token.accessToken; var refreshtoken = token.refreshToken; var uuid = user.subject; Logger().w(uuid); Logger().w(acesstoken); Logger().w(refreshtoken); Logger().w(idtoken.toCompactSerialization()); UserSecureStorage.writedata("idToken", idtoken.toCompactSerialization()); UserSecureStorage.writedata("acessToken", acesstoken.toString()); UserSecureStorage.writedata("refreshToken", refreshtoken.toString()); UserSecureStorage.writedata("userId", uuid); await UserSimplePreferences.writedata( "idToken", idtoken.toCompactSerialization()); var token2 = UserSimplePreferences.readData("idToken"); Logger().v(token2); String idtoken2; UserSecureStorage.readData("idToken").then((value) { idtoken2 = value!; }); Logger().wtf(context); UtilFunctions.pushRemoveNavigation(context, const DocProfile()); setIsLOgin(true); setIsLOading(false); return token; } catch (e) { Logger().e(e); Alert( context: context, type: AlertType.error, title: "Unable To Login", desc: "Something Went Wrong", buttons: [ DialogButton( onPressed: () => Navigator.pop(context), width: 120, child: const Text( "Try Again", style: TextStyle(color: Colors.white, fontSize: 15), ), ) ], ).show(); } notifyListeners(); } Future<void> logout() async { String logoutUrl = "removed"; if (await canLaunch(logoutUrl)) { await launch(logoutUrl, forceWebView: true); } else { throw 'Could not launch $logoutUrl'; } await Future.delayed(const Duration(seconds: 2)); setIsLOgin(false); // UserSecureStorage.clearAll(); closeInAppWebView(); } }
// UserSecureStorage类代码 import 'package:flutter_secure_storage/flutter_secure_storage.dart'; class UserSecureStorage { static const storage = FlutterSecureStorage(); static Future<void> writedata(String key, String value) async { await storage.write(key: key, value: value); } static Future<void> writebool(String key, bool value) async { await storage.write(key: key, value: value.toString()); } static Future<String?> readData(String key) async { return await storage.read(key: key); } static Future<String?> readBool(String key) async { return await storage.read(key: key); } static Future<Map<String, String>> readAllData(String key) async { return await storage.readAll(); } static Future<bool> containsData(String key) async { return await storage.containsKey(key: key); } static Future<void> deleteData(String key) async { await storage.delete(key: key); } static Future<void> deleteAllData() async { await storage.deleteAll(); } }
解决方案
问题根源:并非保存时存入了Future对象,而是读取Secure Storage时直接打印了未完成的Future实例,或是初始化
_isLogin时错误地将Future对象参与布尔判断(仅影响登录状态判断,不影响Token保存)。另外,保存操作未加await可能导致后续逻辑提前执行,但不会改变Token的实际保存值。修复步骤:
- 保存操作添加await:给所有
UserSecureStorage.writedata调用加上await,确保保存完成后再执行后续代码:await UserSecureStorage.writedata("idToken", idtoken.toCompactSerialization()); await UserSecureStorage.writedata("acessToken", acesstoken.toString()); await UserSecureStorage.writedata("refreshToken", refreshtoken.toString()); await UserSecureStorage.writedata("userId", uuid); - 读取时正确处理Future:读取Secure Storage内容必须通过
await获取结果,不能直接打印Future实例:// 错误写法:直接打印Future // Logger().wtf(UserSecureStorage.readData("idToken")); // 正确写法1:使用await String? idtoken2 = await UserSecureStorage.readData("idToken"); Logger().wtf(idtoken2); // 正确写法2:使用then回调 UserSecureStorage.readData("idToken").then((value) { Logger().wtf(value); }); - 修正登录状态初始化逻辑:
UserSecureStorage.readBool返回的是Future,不能直接用于同步布尔判断,改为异步初始化:// 原错误代码 // final bool _isLogin = UserSecureStorage.readBool("isAuth") == true ? true : false; // 改为异步初始化,可在页面initState或类的初始化方法中调用 bool _isLogin = false; Future<void> initAuthStatus() async { String? authStatus = await UserSecureStorage.readBool("isAuth"); _isLogin = authStatus == "true"; notifyListeners(); } - 处理Token空值风险:为避免
acesstoken或refreshtoken为null时保存异常,添加空值判断:await UserSecureStorage.writedata("acessToken", acesstoken ?? ""); await UserSecureStorage.writedata("refreshToken", refreshtoken ?? "");
- 保存操作添加await:给所有
内容的提问来源于stack exchange,提问作者Shakila
相关产品推荐
相关产品推荐

