一键登录彻底简化了用户的操作流程,传统要输入手机号,然后点击发送验证码,再点击登录,使用一键登录就不用输入手机号,直接用本机号码登录。
这里我们使用极光的一键登录【我没收极光的钱哈】
准备材料
- 营业执照
- 安卓app应用签名
app打包后,使用签名工具签名,app打包相关,见我上篇文章 flutter -- 签名、打包 (Android) - iOS开发者账号
需要申请bundleId
申请bundle ID
进入开发者苹果平台,申请开发者,一年需要688人民币,然后申请bundle id,也叫应用id,具体根据提示来,不知道的就搜一下,然后把bundle ID复制下来,粘贴到xcode里面。
申请应用
进入开发者平台极光开发者平台 申请账号后创建应用




安装环境
插件引用
pubspec.yaml
dependencies:
jverify: 0.4.0
安卓配置
\android\app\build.gradle
defaultConfig下面添加如下代码
ndk {
//选择要添加的对应 cpu 类型的 .so 库。
abiFilters 'armeabi', 'armeabi-v7a', 'x86', 'x86_64', 'mips', 'mips64', 'arm64-v8a'
}
manifestPlaceholders = [
JPUSH_PKGNAME : applicationId,
JPUSH_APPKEY : "e579470dae40f090f92c1f3f", // NOTE: JPush 上注册的包名对应的 Appkey.
JPUSH_CHANNEL : "developer-default", //暂时填写默认值即可.
]
IOS配置
无需配置
使用
引入插件
import 'package:jverify/jverify.dart';
初始化
jverify.setDebugMode(true);
jverify.setup(
appKey: "申请的appkey",
channel: "devloper-default"
);
网络校验
jverify.checkVerifyEnable().then((map){
bool result = map["result"];
if(result){
// 当前网络环境支持认证
print('当前网络环境支持认证');
}else{
// 当前网络环境不支持认证
print('no~~当前网络环境不支持认证');
}
});
获取loginAuth
JVUIConfig uiConfig = JVUIConfig();
jverify.setCustomAuthorizationView(false, uiConfig, landscapeConfig: uiConfig, widgets: widgetList);
/// 步骤 2: 添加 loginAuth 接口回调的监听 (如果想通过 loginAuth 接口异步返回获取接口数据,则忽略此步骤)
jverify.addLoginAuthCallBackListener((event){
print("通过添加监听,获取到 loginAuth 接口返回数据,code=${event.code},message = ${event.message},operator = ${event.operator}");
});
/// 步骤 3:开始调用一键登录接口
jverify.loginAuth(true).then((map){
/// 步骤 4:获取 loginAuth 接口异步返回数据(如果是通过添加 JVLoginAuthCallBackListener 监听来获取返回数据,则忽略此步骤)
// print("通过接口异步返回,获取到 loginAuth 接口返回数据,code=$code,message = $content,operator = $operator");
print("通过接口异步返回,获取到 loginAuth");
print(map);
});
(上面widgets,不是必传参数)
在这里,步骤三的数据才是我们需要的。
getToken (目前在一键登陆用不到,做安全校验的时候才用到)
jverify.getToken().then((map){
int _code = map["code"]; // 返回码,2000代表获取成功,其他为失败,详见错误码描述
String _token = map["content"]; // 成功时为token,可用于调用验证手机号接口。token有效期为1分钟,超过时效需要重新获取才能使用。失败时为失败信息
String _operator = map["operator"]; // 成功时为对应运营商,CM代表中国移动,CU代表中国联通,CT代表中国电信。失败时可能为null
print("一键登录 token code: ${_code} 运营商: ${_operator} _token: ${_token}");
if(_code == 2000){
// 请求后台校验
}else{
print('没有手机卡,或者异常');
}
});
运行逻辑
Jverify jverify = new Jverify();
// 是否调试模式
jverify.setDebugMode(true);
// ios使用的参数
jverify.setup(
appKey: "e57947打码c1f3f",
channel: "devloper-default",
useIDFA: false
);
jverify.checkVerifyEnable().then((map){
bool result = map["result"];
if(result){
// 获取loginAuth
loginAuth() // 这部分的代码就是上面 获取loginAuth 的代码
}
})
效果
全部没有报错的话就是成功了
会有一个界面弹出来

点击登录的按钮,就会执行
jverify.loginAuth(true)
这个的异步回调,数据打印出来是如下所示

调用后会自动全屏,不需要引入view到flutter界面里面渲染
异常情况处理
异常1
有一个安卓手机跑起来后是这样,出问题了,QAQ

信号太差了,会有失败的可能性,请在4G+网络下测试。
异常2
2010

原因: takget sdk 版本>22需要动态申请权限。
不过,我们先申请下静态权限
在Android/app/src/main/AndroidManifest.xml
加如下代码。(manifest里面,application之前)
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
最好加下基本的权限
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <!-- 用于开启 debug 版本的应用在6.0 系统上 层叠窗口权限 -->
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
然后修改Android/app/src/build.gradle
(这是不能保证完全没问题,最好是使用动态申请权限,不过测试的时候可以这样做)
targetSdkVersion 22
动态申请权限需要写安卓和iOS两个权限的代码然后封装给flutter使用,不过有现成的插件flutter动态申请权限插件
异常3
4033
I/flutter (11895): {code: 4033, message: appkey is not support login, operator: null}
没有权限,有点奇怪,我什么都明明申请了啊
这里忽略了一个细节,在[应用设置] -> [认证设置] -> [一键登陆], 如图,申请RSA加密,把公钥复制上去,之前我们讲了支付宝支付,里面也是一样的逻辑。

然后提交审核,通过之后再试。
IOS bundle Id设置
ios xocde bundle设置
ios跑的时候需要先在xcode输入bundle ID,不过可能会出现如下错误

后端获取手机号

区别就是,第一个是校验身份,目前不在我们的使用范围呢,也就是安全认证,比如做支付功能,教程目前使用的是是不是本人,就不能发生验证码,直接校验当前使用的手机是不是登录的账号即可。
第二个就是我们要用的
文档上有一个curl
curl --insecure -X POST -v https://api.verification.jpush.cn/v1/web/loginTokenVerify -H "Content-Type: application/json" -u "7d431e42dfa6a6d693ac2d04:5e987ac6d2e04d95a9d8f0d1" -d '{"loginToken":"STsid0000001542695429579Ob28vB7b0cYTI9w0GGZrv8ujUu05qZvw","exID":"1234566"}'
看不懂curl不要紧,有一个文章可以学习下。
curl教程
还有一个是吧,curl转成语言的网址,直接生成代码 curl转各个语言代码, 目前支持蛮多的,nodejs 、java、go、dart、php等

只有根据上面的获取到了phone的密文才能解析,直接解析loginToken(就是flutter获取到的token),是不行滴,这个要注意,我卡这里好久了QAQ
用nodejs换取手机号密钥
根据点击一键登陆获取到的token,使用CURL执行,会返回9210(提示参数错误),可以用语言代码解析,我放个nodejs换取手机号密文成功的代码
var request = require('request');
var headers = {
'Content-Type': 'application/json'
};
var loginToken = "tg9Z2evKEO/rD7naE1JnCYTbMLirZZgn45FrbAynCA0aukCbC8dzjuWAHI+d9ZjB4vs1ac25U8cJxkGbsBZGXssXpn7aR0vQ0M977qIbkDz/e9DsD+ueQFt/iWHV+ZR4YoDYf9+lMrePqLzTyOx4RiDakHQrLUlUC6G7kznJCSI="
var params = {
loginToken: loginToken,
exID:""
}
var dataString = JSON.stringify(params)
var options = {
url: 'https://api.verification.jpush.cn/v1/web/loginTokenVerify',
method: 'POST',
headers: headers,
body: dataString,
auth: {
'user': '这个是应用的key',
'pass': '这个是应用服务端的密码'
}
};
function callback(error, response, body) {
if (!error && response.statusCode == 200) {
console.log(body);
}
}
request(options, callback);
手机号密文解密
JAVA 解密
import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
public class RSADecrypt {
public static void main(String[] args) throws Exception {
String encrypted = "iZT省略20字veq0=";
String prikey = "MII省略20字Kqw==";
String publicKey = "省略20字";
System.out.println("解密 result 111");
String result = decrypt(encrypted, prikey);
System.out.println(result);
}
public static String decrypt(String cryptograph, String prikey) throws Exception {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(prikey));
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec);
Cipher cipher=Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte [] b = Base64.getDecoder().decode(cryptograph);
return new String(cipher.doFinal(b));
}
}
亲测可以,如果报错,应该是密文问题。
如有需要可以下方评论找我要加密的方法,pkcs8 java加解密由皮皮鼠(oops)提供。
(如果用自己的解密方法,报错,如果方法没有问题,那么就是少了一个步骤,解密传入的密文需要base64解码,代码: Base64.getDecoder().decode
)
有一个很重要的一点,pkcs8适合java,其他语言可以用工具把pkcs8转pkcs1,支付宝开放平台有一个转换的地方

python安装pycryptodome
报错了。
php
<?php
$prefix = '-----BEGIN RSA PRIVATE KEY-----';
$suffix = '-----END RSA PRIVATE KEY-----';
$result = '';
$encrypted = null;
$prikey = null;
$key = $prefix . "\n" . $prikey . "\n" . $suffix;
$r = openssl_private_decrypt(base64_decode($encrypted), $result, openssl_pkey_get_private($key));
echo $result . "\n";
nodejs
const crypto = require('crypto')
var privateStr = `MIIXXXXXXXXXXXXXXKqw==`
var publicStr = `MxxxxXXXXXXXXXXXXXxxxQAB`
var 公钥pem = `-----BEGIN PUBLIC KEY-----\n${publicStr.replace(/(.{64})/g, '$1\n')}\n-----END PUBLIC KEY-----`.toString('ascii')
var 私钥pem = `-----BEGIN PRIVATE KEY-----\n${privateStr}\n-----END PRIVATE KEY-----`
var 一键登陆密文 = "";
//私钥解密
const decodeData = crypto.privateDecrypt({key: 私钥pem, padding: crypto.constants.RSA_PKCS1_PADDING}, Buffer.from(一键登陆密文, 'base64'));
console.log("手机号: ", decodeData.toString())
这里有一个需要注意的地方
Buffer.from(密文, 'base64') 不等于 new Buffer(
new Buffer(密文.toString('base64'), 'base64').toString(),
'utf-8'
)
(如果密钥公钥没有换行的话,请直接用上面的代码,如果已经下载就是pem文件,那么直接替换[私钥/公钥]pem)
nodejs这一块我摸索了好久,如果有帮助,请点个赞,谢谢~~
相关链接
github.com/jpush/jveri…
docs.jiguang.cn/jverificati…
github.com/jpush/jveri…
github.com/BaseflowIT/…