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

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的实际保存值。

  • 修复步骤:

    1. 保存操作添加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);
      
    2. 读取时正确处理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);
      });
      
    3. 修正登录状态初始化逻辑: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();
      }
      
    4. 处理Token空值风险:为避免acesstoken或refreshtoken为null时保存异常,添加空值判断:
      await UserSecureStorage.writedata("acessToken", acesstoken ?? "");
      await UserSecureStorage.writedata("refreshToken", refreshtoken ?? "");
      

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 15:30:52