Flutter 集成一键登录

1,116 阅读5分钟

登录方式的演变流程

账号+密码登录

传统的登录方式,最普遍的登录方式,用户注册时输入账号和密码,虽然简单粗暴,也确实能够满足用户鉴权的场景,但缺点也是显而易见:

用户层面:记忆成本高、账号泄漏影响面广。

企业层面:恶意注册。

手机号+验证码

手机号+验证码和账号+密码相比,不需要用户记住的账号密码,也增加了安全性,可以更好的验证用户身份并防止恶意注册。

但是这种方式依然存在缺点:输入成本高、依赖短信、存在验证码泄漏风险。

本机号码认证

虽然操作系统限制用户端无法直接获取手机号,但是运营商可以通过 sim 卡流量数据查询到,因此号码认证应运而生。

现在国内三大运营商(移动、联通、电信)都已经开放了相关的能力:用户输入手机号之后请求运营商接口,运营商拿到用户输入的手机号并通过请求网络流量查询 sim 卡对应的手机号,判断两者是否一致,即可完成登录,极大的简化了登录流程,提高了安全性。

一键登录

简单来讲就是获取当前用户当前手机号,直接用该手机号进行登录,这种登录方式特点是一键式,完美解决了上述登录方式存在记忆成本、输入成本、泄漏风险、恶意注册等所有问题,也极大程度降低了注册登录环节的用户流失。

选项账号+密码手机号+验证码本机号码认证一键登录
用户记忆成本
用户输入成本中低
用户泄漏风险
恶意注册风险

一键登录的基本原理

1.2 判断手机号归属运营商: 需要通过手机流量上网才能支持一键登录。

2.1 对应运营商发起预取号: 预取号的目的是获取手机号掩码。

3.3 用户确认授权一键登录之后,用户端会向运营商发起请求以获取登录 Token,运营商服务端会返回一个登录 Token 给用户端,应用再通过自己的业务服务端拿着这个 Token 找运营商换取手机号码。

4.3返回手机号: 成功返回手机号后,可以说是已经登录成功了,应用就需要维护自己用户的登录状态,这里可以采用传统的 Session 机制,也可以使用 JWT 机制。

集成一键登录功能

pubspec.yaml文件下面配置jverify插件依赖

Android 工程里面配置appkey

manifestPlaceholders = [
        JPUSH_PKGNAME: defaultConfig.applicationId,
        JPUSH_APPKEY : "@string/push_app_key",
        JPUSH_CHANNEL: "developer-default",
]

jverify插件初始化

Future<void> initPlatformState(BuildContext context,
    {VoidCallback? initSuccessCallBack}) async {
  // 初始化 SDK 之前添加监听
  jVerify.addSDKSetupCallBackListener((JVSDKSetupEvent event) {
    logV("极光一键登录SDK初始化结果 :${event.toMap()}");
    Map? initMap = event.toMap();
    int code = initMap['code'];

    /// 初始化成功
    if ('$code' == '8000') {
      initSuccessCallBack!();
    }
  });
  jVerify.setDebugMode(true); // 打开调试模式
  jVerify.setCollectionAuth(true);
  jVerify.setup(
      //"你自己应用的 AppKey",
      appKey: await jpushAppKeyMetaValue(),
      // 初始化sdk,  appKey 和 channel 只对ios设置有效
      channel: "developer-default");
  if (!(context.mounted)) return;

  /// 授权页面点击时间监听
  jVerify.addAuthPageEventListener((JVAuthPageEvent event) {
    //logV("receive auth page event :${event.toMap()}");
  });
}

SDK 请求授权一键登录

/// SDK 请求授权一键登录
void loginAuth(BuildContext context, {IJVLoginCallBack? loginCallBack}) {
  jVerify.checkVerifyEnable().then((map) {
    bool result = map['result'] ?? false;
    logV('SDK 请求授权一键登录 结果 $map');
    if (result) {
      final screenSize = MediaQuery.of(context).size;
      final screenWidth = screenSize.width;
      final screenHeight = screenSize.height;
      bool isiOS = Platform.isIOS;

      /// 自定义授权的 UI 界面,以下设置的图片必须添加到资源文件里,
      /// android项目将图片存放至drawable文件夹下,可使用图片选择器的文件名,
      /// 例如:btn_login.xml,入参为"btn_login"。
      /// ios项目存放在 Assets.xcassets。
      JVUIConfig uiConfig = JVUIConfig();
      uiConfig.statusBarDarkMode = true;
      uiConfig.navColor = Colors.white.value;
      uiConfig.navText = "登录";
      uiConfig.navTextColor = const Color(0xff000000).value;
      uiConfig.navReturnImgPath = "return_bg"; //图片必须存在

      /// 一键登录顶部Logo
      uiConfig.logoWidth = 100.w.toInt();
      uiConfig.logoHeight = 100.w.toInt();
      uiConfig.logoOffsetY = 93.w.toInt();
      uiConfig.logoVerticalLayoutItem = JVIOSLayoutItem.ItemSuper;
      uiConfig.logoHidden = false;
      uiConfig.logoImgPath = "logo";
      //  uiConfig.popViewCornerRadius = 5;

      /// 电话号码
      if (isiOS) {
        uiConfig.numberFieldWidth = 260.w.toInt();
        uiConfig.numberFieldHeight = 50.w.toInt();
        uiConfig.numFieldOffsetY = isiOS ? 185.w.toInt() : 365.w.toInt();
        uiConfig.numberVerticalLayoutItem = JVIOSLayoutItem.ItemLogo;
        uiConfig.numberColor = Colors.black.value;
        uiConfig.numberTextBold = true;
        uiConfig.numberSize = 30.sp.toInt();
      } else {
        uiConfig.numberFieldWidth = 260.w.toInt();
        uiConfig.numFieldOffsetY = isiOS ? 100.w.toInt() : 365.w.toInt();
        uiConfig.numberVerticalLayoutItem = JVIOSLayoutItem.ItemLogo;
        uiConfig.numberColor = Colors.black.value;
        uiConfig.numberTextBold = true;
        uiConfig.numberSize = 30.sp.toInt();
      }

      /// 隐藏 ”中国移动“ 文本提示

      uiConfig.sloganHidden = true;

      /// 一键登录按钮
      uiConfig.logBtnWidth = 345.w.toInt();
      uiConfig.logBtnHeight = 48.w.toInt();
      if (isiOS) {
        /// 距离顶部的高度
        uiConfig.logBtnOffsetY = 445.w.toInt();
      } else {
        uiConfig.logBtnOffsetY = 445.w.toInt();
      }
      uiConfig.logBtnVerticalLayoutItem = JVIOSLayoutItem.ItemSlogan;
      uiConfig.logBtnText = "一键登录";
      uiConfig.logBtnTextColor = Colors.white.value;
      uiConfig.logBtnTextSize = 15.sp.toInt();
      uiConfig.logBtnTextBold = false;
      uiConfig.logBtnBackgroundPath = 'login_btn_normal';
      uiConfig.loginBtnNormalImage = "login_btn_normal"; //图片必须存在
      uiConfig.loginBtnPressedImage = "login_btn_press"; //图片必须存在
      uiConfig.loginBtnUnableImage = "login_btn_unable"; //图片必须存在

      //only android 设置隐私条款不选中时点击登录按钮默认显示toast。
      uiConfig.privacyHintToast = true;

      uiConfig.privacyState = false; //设置默认勾选
      uiConfig.privacyCheckboxSize = 14.w.toInt();
      uiConfig.checkedImgPath = "check_image"; //图片必须存在
      uiConfig.uncheckedImgPath = "uncheck_image"; //图片必须存在
      uiConfig.privacyCheckboxInCenter = true;
      uiConfig.privacyCheckboxHidden = false;
      uiConfig.isAlertPrivacyVc = true;

      if (Platform.isIOS) {
        uiConfig.privacyOffsetX = 27.w.toInt();
        uiConfig.privacyOffsetY = 30.w.toInt(); // 距离底部距离
        uiConfig.privacyVerticalLayoutItem = JVIOSLayoutItem.ItemSuper;
        uiConfig.clauseBaseColor = const Color(0xff999999).value;
        uiConfig.clauseColor = const Color(0xff999999).value;
        uiConfig.privacyText = ["登录即代表您同意并阅读"];
        uiConfig.privacyTextSize = 13.sp.toInt();
        uiConfig.sloganHidden = true;
        uiConfig.privacyItem = [
          JVPrivacy("隐私政策", AgreePath.priAgree, separator: "以及"),
        ];
      } else {
        uiConfig.privacyOffsetX = 27.w.toInt();
        uiConfig.privacyOffsetY = 15.w.toInt(); // 距离底部距离
        uiConfig.privacyVerticalLayoutItem = JVIOSLayoutItem.ItemSuper;
        uiConfig.clauseBaseColor = const Color(0xff999999).value;
        uiConfig.clauseColor = const Color(0xff999999).value;
        uiConfig.privacyText = ["登录即代表您同意并阅读"];
        uiConfig.privacyTextSize = 13.sp.toInt();
        uiConfig.privacyItem = [
          JVPrivacy("隐私政策", AgreePath.priAgree, separator: "以及"),
        ];
      }

      uiConfig.textVerAlignment = 1;
      uiConfig.authStatusBarStyle = JVIOSBarStyle.StatusBarStyleDarkContent;
      uiConfig.privacyStatusBarStyle = JVIOSBarStyle.StatusBarStyleDefault;
      uiConfig.modelTransitionStyle = JVIOSUIModalTransitionStyle.CrossDissolve;

      uiConfig.statusBarColorWithNav = true;
      uiConfig.statusBarDarkMode = true;
      uiConfig.virtualButtonTransparent = true;

      uiConfig.privacyStatusBarColorWithNav = false;
      uiConfig.privacyVirtualButtonTransparent = false;

      uiConfig.needStartAnim = true;
      uiConfig.needCloseAnim = true;
      uiConfig.enterAnim = "activity_slide_enter_bottom";
      uiConfig.exitAnim = "activity_slide_exit_bottom";

      uiConfig.privacyNavColor = Colors.white.value;
      uiConfig.privacyNavTitleTextColor = Colors.black.value;
      uiConfig.privacyNavTitleTextSize = 16.sp.toInt();

      uiConfig.privacyNavReturnBtnImage = "return_bg"; //图片必须存在;

      //协议内容设置 -iOS
      if (isiOS) {
        uiConfig.agreementAlertViewTitleTexSize = 18.sp.toInt();
        uiConfig.agreementAlertViewTitleTextColor = Colors.black87.value;
        uiConfig.agreementAlertViewContentTextAlignment =
            JVTextAlignmentType.center;
        uiConfig.agreementAlertViewContentTextFontSize = 16.sp.toInt();
        uiConfig.agreementAlertViewLoginBtnNormalImagePath = "login_btn_normal";
        uiConfig.agreementAlertViewLoginBtnPressedImagePath = "login_btn_press";
        uiConfig.agreementAlertViewLoginBtnUnableImagePath = "login_btn_unable";
        uiConfig.agreementAlertViewLogBtnTextColor = Colors.white.value;
      } else {
        //协议二次弹窗内容设置 -Android (这里不设置)
        uiConfig.privacyCheckDialogConfig = null;
      }

      //协议页面是否支持暗黑模式
      uiConfig.setIsPrivacyViewDarkMode = false;

      /// 添加自定义的 控件 到授权界面
      List<JVCustomWidget> widgetList = [];

      ///
      widgetList.add(jvTextViewAppName(screenWidth: screenWidth));
      widgetList.add(jvTextViewOtherPhone(screenWidth: screenWidth));
      widgetList.add(jvTextviewTips(screenWidth: screenWidth));

      /// 步骤 1:调用接口设置 UI
      jVerify.setCustomAuthorizationView(true, uiConfig,
          landscapeConfig: uiConfig, widgets: widgetList);

      /// 步骤 2:调用一键登录接口
      jVerify.loginAuthSyncApi2(
          autoDismiss: true,
          loginAuthcallback: (event) {
            Map? map = event.toMap();
            logV('调用一键登录接口 $map');
            var code = map['code'];
            if ('$code' == '6000') {
              /// 获取一键登录token
              var token = map['message'] ?? '';
              loginCallBack!(token);
            } else {
              ///showShortToast('一键登录失败');
            }
          });
    } else {
      ///showShortToast('一键登录请求授权失败');
    }
  });
}

一键登录效果

一键登录案例

flutter一键登录案例集成