1.开通极光认证
极光官网创建应用后,在【认证设置】的【集成设置】中填写信息,Android的应用签名需要下载签名工具获取,ios填写Bundle ID,填写完毕后提交审核
在【认证设置】的【一键登录】中填写RSA 加密公钥
加密公钥在在线生成公钥私钥对,RSA公私钥生成-ME2在线工具中获取,开通需要集成的平台
注意:RSA加密公钥位数1024位,密钥格式PKCS#8
2.引入插件及相关配置
dependencies:
jverify: 2.2.8
在android/app/build.gradle文件中进行以下配置:
android: {
...
manifestPlaceholders = [
JPUSH_PKGNAME : applicationId, //填写信息时的应用包名
JPUSH_APPKEY : "appkey", // 极光后台该应用对应的Appkey
JPUSH_CHANNEL : "developer-default", //暂时填写默认值即可.
]
...
}
3. 极光认证初始化
在登录界面initState进行初始化:
import 'package:jverify/jverify.dart';
final Jverify jverify = new Jverify();
/// 统一 key
final String f_result_key = "result";
/// 错误码
final String f_code_key = "code";
/// 回调的提示信息,统一返回 flutter 为 message
final String f_msg_key = "message";
/// 运营商信息
final String f_opr_key = "operator";
@override
void initState() {
await initPlatformState();
await isInitSuccess();
await preLogin();
}
void initPlatformState() {
// 初始化 SDK 之前添加监听
jverify.addSDKSetupCallBackListener((JVSDKSetupEvent event) {
print("receive sdk setup call back event :${event.toMap()}");
});
// 是否打开调试模式
jverify.setDebugMode(true);
// 初始化sdk
jverify.setup(
appKey: "填写应用的AppKey",
channel: "devloper-default"
);
// 授权页面点击事件监听
jverify.addAuthPageEventListener((JVAuthPageEvent event) {
print("receive auth page event :${event.toMap()}");
});
}
/// sdk 初始化是否完成
void isInitSuccess() {
jverify.isInitSuccess().then((map) {
bool result = map[f_result_key];
if (result) {
print("极光一键登录sdk 初始化成功");
} else {
print("极光一键登录sdk 初始化失败");
}
});
}
// 登录预取号
void preLogin() {
// 判断当前的手机网络环境是否可以使用认证。
jverify.checkVerifyEnable().then((map) {
bool result = map[f_result_key];
if (result) {
jverify.preLogin().then((map) {
print("预取号接口回调:${map.toString()}");
int code = map[f_code_key];
String message = map[f_msg_key];
print("[$code] message = $message");
});
} else {
print("[2016],msg = 当前网络环境不支持认证");
}
});
}
4.点击一键登录按钮拉起授权页
在登录页我们需要一个一键登录的按钮,点击后拉起极光的授权页
GestureDetector(
onTap: (){
loginAuth();
},
child: Text(
'一键登录'
),
),
/// SDK 请求授权一键登录
void loginAuth() {
jverify.checkVerifyEnable().then((map) {
bool result = map[f_result_key];
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.authBackgroundImage = 'background_image';
// 导航栏
uiConfig.navHidden = isiOS ? true : true;
uiConfig.navReturnBtnHidden = false;
uiConfig.navColor = isiOS ? Color(0xFF0C0D16).value : Colors.transparent.value;
uiConfig.navText = " ";
uiConfig.navTextColor = Colors.blue.value;
uiConfig.navReturnImgPath = "return_bg"; //图片必须存在
// logo
uiConfig.logoWidth = 100;
uiConfig.logoHeight = 100;
uiConfig.logoOffsetY = 10;
uiConfig.logoVerticalLayoutItem = JVIOSLayoutItem.ItemSuper;
uiConfig.logoHidden = false;
uiConfig.logoImgPath = "logo";
uiConfig.logoHidden = true;
// 号码
uiConfig.numberFieldWidth = 200;
uiConfig.numberFieldHeight = 40;
uiConfig.numFieldOffsetY = isiOS ? 144 : 144;
uiConfig.numberVerticalLayoutItem = isiOS ? JVIOSLayoutItem.ItemLogo : JVIOSLayoutItem.ItemNone;
uiConfig.numberColor = Colors.white.value;
uiConfig.numberSize = 26;
// slogan
uiConfig.sloganOffsetY = isiOS ? 20 : 180;
uiConfig.sloganVerticalLayoutItem = JVIOSLayoutItem.ItemNumber;
uiConfig.sloganTextColor = Color(0xFF6E6E6E).value;
uiConfig.sloganTextSize = 14;
uiConfig.sloganHidden = true;
// 登录按钮
uiConfig.logBtnWidth = MediaQuery.of(_context).size.width.toInt() - 100;
uiConfig.logBtnHeight = 50;
uiConfig.logBtnOffsetY = isiOS ? 350 : 500;
uiConfig.logBtnVerticalLayoutItem = isiOS ? JVIOSLayoutItem.ItemSlogan : JVIOSLayoutItem.ItemPrivacy;
uiConfig.logBtnText = "一键登录";
uiConfig.logBtnTextColor = Colors.white.value;
uiConfig.logBtnTextSize = 18;
uiConfig.logBtnTextBold = true;
uiConfig.logBtnBackgroundPath = "login_btn"; //图片必须存在
uiConfig.loginBtnNormalImage = "loginbtn"; //图片必须存在 only ios
uiConfig.loginBtnPressedImage = "loginbtn"; //图片必须存在 only ios
uiConfig.loginBtnUnableImage = "loginbtn"; //图片必须存在 only ios
// 隐私协议栏
uiConfig.privacyHintToast = true; //设置隐私条款不选中时点击登录按钮默认显示toast。
uiConfig.privacyState = false; //设置隐私条款默认选中状态,默认不选中
uiConfig.privacyCheckboxSize = 20;
uiConfig.privacyCheckboxInCenter = true; //设置隐私条款checkbox是否相对协议文字纵向居中
uiConfig.privacyCheckboxHidden = false; //设置隐私条款checkbox是否隐藏
uiConfig.privacyOffsetY = 15; // 隐私条款相对于授权页面底部下边缘 y 偏移
uiConfig.privacyVerticalLayoutItem = JVIOSLayoutItem.ItemSuper;
uiConfig.clauseColor = Color(0xFFFEFEFE).value;
uiConfig.privacyText = ["登录即代表同意"];
uiConfig.privacyTextSize = 13;
uiConfig.textVerAlignment = 1; //设置条款文字是否垂直居中对齐(默认居中对齐) 0是top 1是m 2是b
uiConfig.privacyWithBookTitleMark = true; //设置隐私条款运营商协议名是否加书名号
uiConfig.privacyTextCenterGravity = true; //隐私条款文字是否居中对齐(默认左对齐)
// 授权页
uiConfig.statusBarColorWithNav = true; //授权页状态栏是否跟导航栏同色 only android
uiConfig.authStatusBarStyle = JVIOSBarStyle.StatusBarStyleDarkContent; //授权页状态栏样式设置 only iOS
uiConfig.virtualButtonTransparent = true; //授权页虚拟按键背景是否透明 only android
// 隐私协议 web 页 UI 配置
uiConfig.privacyStatusBarStyle = JVIOSBarStyle.StatusBarStyleDefault; //隐私协议web页 状态栏样式设置 only iOS
uiConfig.privacyNavColor = Color(0xFF0C0D16).value; // 导航栏颜色
uiConfig.privacyNavTitleTextColor = Colors.white.value; // 标题颜色
uiConfig.privacyNavTitleTextSize = 16; // 标题大小
uiConfig.privacyNavReturnBtnImage = "back"; //图片必须存在;
// 授权页弹窗模式 配置,选填
uiConfig.modelTransitionStyle = JVIOSUIModalTransitionStyle.CrossDissolve; //弹出方式 only ios
// 隐私页
uiConfig.privacyStatusBarColorWithNav = true; //隐私页web状态栏是否与导航栏同色 only android
uiConfig.privacyVirtualButtonTransparent = true; //隐私页web页虚拟按键背景是否透明 only android
//是否需要动画
uiConfig.needStartAnim = true; //设置拉起授权页时是否需要显示默认动画
uiConfig.needCloseAnim = true; //设置关闭授权页时是否需要显示默认动画
uiConfig.enterAnim = "activity_slide_enter_bottom"; // 拉起授权页时进入动画 only android
uiConfig.exitAnim = "activity_slide_exit_bottom"; // 退出授权页时动画 only android
// 添加自定义的 控件 到授权界面
List<JVCustomWidget> widgetList = [];
/// 步骤 1:调用接口设置 UI
jverify.setCustomAuthorizationView(true, uiConfig,landscapeConfig: uiConfig, widgets: widgetList);
/// 步骤 2:调用一键登录接口,极光提供了两种方式:同步(loginAuthSyncApi)和异步(loginAuth)
/// 我使用的是同步,需要使用异步的在极光文档查看
/// 先,添加 loginAuthSyncApi 接口回调的监听
jverify.addLoginAuthCallBackListener((event) {
// 当code为6000时,message就是我们需要的token
print("监听获取返回数据:[${event.code}] message = ${event.message}");
print("通过添加监听,获取到 loginAuthSyncApi 接口返回数据,code=${event.code},message = ${event.message},operator = ${event.operator}");
String message = event.message;
getPhone(message);
});
/// 再,执行同步的一键登录接口
jverify.loginAuthSyncApi(autoDismiss: true);
} else {
print("[2016],msg = 当前网络环境不支持认证");
toast('当前网络环境不支持一键登录');
}
});
}
// 将token传给后端,获取手机号
void getPhone(String token) async {
String url = "https://api.verification.jpush.cn/v1/web/loginTokenVerify";
Dio dio = new Dio();
dio.options.contentType = "application/json";
// 注意!!!将appKey和masterSecret以“appKey:masterSecret”的格式转为base64
dio.options.headers = {
"Authorization":"Basic 此处为转为base64的字符串",
};
Map<String, dynamic> params = {
"loginToken": token,
"exID": '',
};
print("调用极光接口:$url 参数:$params");
Response response = await dio.post(url, data: params);
var data = response.data;
String result = json.encode(data);
Map<String, dynamic> user = convert.jsonDecode(result);
print('极光返回值:$user');
// 此处调后端一键登录接口,user['phone']是获取到的加密的手机号码,后端需要使用极光后台填写的公钥对应的私钥进行解密,方法内写登录逻辑即可
oneClickLogin(user['phone']);
}
到此处极光一键登录的功能实现了
过程中还遇到了一些问题:
1.登录预取号jverify.preLogin()失败
可能是连接了WiFi网络的问题,关闭WiFi使用移动数据流量后成功
2.调用addLoginAuthCallBackListener时获取不到token,返回code:6001,message:fetch loginToken failed
这个问题把日志发给极光后反馈预取号过期,尝试运行了极光的demo后又运行了自己的项目又可以了......一行代码都没改。
后续又出现过这个问题,拉不起授权页,code有时返回6001有时返回2005,发日志给极光后反馈是运营商返回的报错,在网络双开或用户网络状况不好的情况下会发生。
总之感觉极光的一键登录不是很稳定,不是100%能使用。