登录方式的演变流程
账号+密码登录
传统的登录方式,最普遍的登录方式,用户注册时输入账号和密码,虽然简单粗暴,也确实能够满足用户鉴权的场景,但缺点也是显而易见:
用户层面:记忆成本高、账号泄漏影响面广。
企业层面:恶意注册。
手机号+验证码
手机号+验证码和账号+密码相比,不需要用户记住的账号密码,也增加了安全性,可以更好的验证用户身份并防止恶意注册。
但是这种方式依然存在缺点:输入成本高、依赖短信、存在验证码泄漏风险。
本机号码认证
虽然操作系统限制用户端无法直接获取手机号,但是运营商可以通过 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('一键登录请求授权失败');
}
});
}
一键登录效果
一键登录案例