uniapp 手机号码一键登录保姆级教程

4,936 阅读9分钟

背景

通过uniapp来开发App,目前内部上架的App产品现有的登录方式有「账号/密码」 和 「手机号/验证码」两种登录方式;但这两种方式还是不够便捷,目前「手机号一键登录」是替代短信验证登录的下一代登录验证方式,能消除现有短信验证模式等待时间长、操作繁琐和容易泄露的痛点。

因此,结合市面上的主流App应用,以及业务方的需求,我们的App产品也需要增加「手机号一键登录」功能。 DCloud联合个推公司整合了三大运营商网关认证的服务,通过运营商的底层SDK,实现App端无需短信验证码直接获取手机号。

uni官方提供了对接的方案文档,可自行查阅,也可继续阅读本文

准备工作

1 目前支持的版本及运营商

  • 支持版本:HBuilderX 3.0+
  • 支持项目类型:uni-app的App端,5+ App,Wap2App
  • 支持系统平台: Android,iOS
  • 支持运营商: 中国移动,中国联通,中国电信

2 费用

2.1 运营商费用

目前一键登录收费规则为每次登录成功请求0.02元,登录失败则不计费。

2.2 云空间费用

开通uniCloud是免费的,其中阿里云是全免费,腾讯云是提供一个免费服务空间。

阿里云

选择阿里云作为服务商时,服务空间资源完全免费,每个账号最多允许创建50个服务空间。阿里云目前处于公测阶段,如有正式业务对稳定性有较高要求建议使用腾讯云。

image.png

阿里云的服务空间是纯免费的。但为避免资源滥用,有一些限制,见下:

image.png

除上面的描述外,阿里云没有其他限制。 因为阿里云免费向DCloud提供了硬件资源,所以DCloud也没有向开发者收费。如果阿里云后续明确了收费计划,DCloud也会第一时间公布。

腾讯云

选择腾讯云作为服务商时,可以创建一个免费的服务空间,资源详情参考腾讯云免费额度;如想提升免费空间资源配额,或创建更多服务空间,则需付费购买。

image.png

2.3 云函数费用

如果你的一键登录业务平均每天获取手机号次数为10000次,使用阿里云正式版云服务空间后,对应云函数每天大概消耗0.139元

接入

1 重要前置条件

  • 手机安装有sim卡
  • 手机开启数据流量(与wifi无关,不要求关闭wifi,但数据流量不能禁用。)
  • 开通uniCloud服务(但不要求所有后台代码都使用uniCloud)
  • 开发者需要登录 DCloud开发者中心,申请开通一键登录服务。

2 开发者中心-开通一键登录服务

此官方文档详细步骤开通一键登录服务,开通后将当前项目加入一键登录内,审核2-3天;

3 开通uniCloud

一键登录在客户端获取 access_token 后,必须通过调用uniCloud中云函数换取手机号码, 所以需要开通uniCould;

登录uniCloud中web控制台里,新建服务空间,开通uniCloud

在uniCloud的云函数中拿到手机号后,可以直接使用,也可以再转给传统服务器处理,也可以通过云函数url化方式生成普通的http接口给5+ App使用。

4 客户端-一键登录

当前项目关联云空间

项目名称点击右键,创建云环境,创建的云环境应与之前开通的云空间类型保持一致,我这里选择腾讯云;

image.png

创建好后当前项目下会多个文件夹「uniCloud」,点击右键关联创建好的云空间

image.png

image.png

关联成功

image.png

获取可用的服务提供商(暂时作用不大)

一键登录对应的 provider ID为 'univerify',当获取provider列表时发现包含 'univerify' ,则说明当前环境打包了一键登录的sdk;

uni.getProvider({
  service: 'oauth',
  success: function (res) {
    console.log(res.provider)// ['qq', 'univerify']
  }
});

参考文档

预登录(可选)

预登录操作可以判断当前设备环境是否支持一键登录,如果能支持一键登录,此时可以显示一键登录选项;

uni.preLogin({
	provider: 'univerify',
	success(){  //预登录成功
		// 显示一键登录选项
	},
	fail(res){  // 预登录失败
		// 不显示一键登录选项(或置灰)
    // 根据错误信息判断失败原因,如有需要可将错误提交给统计服务器
		console.log(res.errCode)
		console.log(res.errMsg)
	}
})

参考文档

请求登录授权

弹出用户授权界面。根据用户操作及授权结果返回对应的回调,拿到 access_token,此时客户端登录认证完成;设置自定义按钮等;后续「需要将此数据提交到服务器获取手机号码」

uni.login({
	provider: 'univerify',
	univerifyStyle: { // 自定义登录框样式
    //参考`univerifyStyle 数据结构`
  },
	success(res){ // 登录成功 在该回调中请求后端接口,将access_token传给后端
		console.log(res.authResult);  // {openid:'登录授权唯一标识',access_token:'接口返回的 token'} 
	},
	fail(res){  // 登录失败
		console.log(res.errCode)
		console.log(res.errMsg)
	}
})

参考文档

获取用户是否选中了勾选框

新增判断是否勾选一键登录相关协议函数;

uni.getCheckBoxState({
	success(res){
		console.log(res.state) // Boolean 用户是否勾选了选框
		console.log(res.errMsg)
	},
	fail(res){
		console.log(res.errCode)
		console.log(res.errMsg)
	}
})

参考文档

用access_token换手机号

客户端获取到 access_token 后,传递给uniCloud云函数,云函数中通过uniCloud.getPhoneNumber方法获取真正的手机号。

换取手机号有三种方式:

  1. 在前端直接写 uniCloud.callFunction ,将 access_token 传给指定的云函数。但需要在「云函数内部」请求服务端接口并将电话号码传到服务器;

  2. 使用普通ajax请求提交 access_token 给uniCloud的云函数(不考虑);

  3. 使用普通ajax请求提交 access_token 给自己的传统服务器,通过自己的传统服务器再转发给 uniCloud 云函数。但uniCloud上的「云函数需要做URL化」;

我们目前使用的是第三种,防止电话号码暴露到前端,通过java小伙伴去请求uniCloud云函数,返回电话号码给后端;

// 云函数验证签名,此示例中以接受GET请求为例作演示
const crypto = require('crypto')
exports.main = async(event) => {
  
  const secret = 'your-secret-string' // 自己的密钥不要直接使用示例值,且注意不要泄露
  const hmac = crypto.createHmac('sha256', secret);
  
  let params = event.queryStringParameters
  const sign = params.sign
  delete params.sign
  const signStr = Object.keys(params).sort().map(key => {
    return `${key}=${params[key]}`
  }).join('&')
  
  hmac.update(signStr);
  
  if(sign!==hmac.digest('hex')){
    throw new Error('非法访问')
  }
  
  const {
    access_token,
    openid
  } = params
  const res = await uniCloud.getPhoneNumber({
  	provider: 'univerify',
        appid: 'xxx', // DCloud appid,不同于callFunction方式调用,使用云函数Url化需要传递DCloud appid参数
  	apiKey: 'xxx', // 在开发者中心开通服务并获取apiKey
  	apiSecret: 'xxx', // 在开发者中心开通服务并获取apiSecret
  	access_token: access_token,
  	openid: openid
  })
  // 返回手机号给自己服务器
  return res
}

res结果

{
	"data": {
		"code": 0,
		"success": true,
		"phoneNumber": "166xxxx6666"
	},
	"statusCode": 200,
	"header": {
		"Content-Type": "application/json; charset=utf-8",
		"Connection": "keep-alive",
		"Content-Length": "53",
		"Date": "Fri, 06 Nov 2020 08:57:21 GMT",
		"X-CloudBase-Request-Id": "xxxxxxxxxxx",
		"ETag": "xxxxxx"
	},
	"errMsg": "request:ok"
}

参考文档

客户端关闭一键登录授权界面

请求登录认证操作完成后,不管成功或失败都不会关闭一键登录界面,需要主动调用closeAuthView方法关闭。完成业务服务登录逻辑后通知客户端关闭登录界面。

uni.closeAuthView()

参考文档

错误码

一键登录相关的错误码

但其中状态码30006,官方未给出相关的说明,但与相关技术沟通得知,该状态码是运营商返回的,大概率是网络信号不好,或者其它等原因造成的,没办法修复,只能是想办法兼容改错误;

目前我们的兼容处理方案是:程序检测判断如果出现该状态码,则关闭一键登录授权页面,并跳转到原有的「手机号验证码」登录页面

参考文档

5 云函数-一键登录

自HBuilderX 3.4.0起云函数需启用uni-cloud-verify之后才可以调用getPhoneNumber接口,扩展库uni-cloud-verify

需要在云函数的package.json内添加uni-cloud-verify的引用即可为云函数启用此扩展,无需做其他调整,因为HbuilderX内部已经集成了该扩展库,只需引入即可,不用安装,代码如下:

{
	"name": "univerify",
	"extensions": {
		"uni-cloud-verify": {} // 启用一键登录扩展,值为空对象即可
	}
}

参考文档

6 运行基座和打包

使用uni一键登录,不需要制作自定义基座,使用HBuilder标准真机运行基座即可。在云函数中配置好apiKey、apiSecret后,只要一键登录成功,就会从你的账户充值中扣费。

在菜单中配置模块权限

image.png

参考文档

需要注意的问题

1. 开通手机号一键登录是否同时需要开通苹果登录?

目前只开通手机号一键登录,未开通苹果登录,在我们项目里是可以的,但是App云打包时是会弹框提示的,但是并不影响项目在App Store中发布;

2. 如果同一个token多次反复获取手机号会重复扣费么?

不会,这种场景应该仅限于联调测试使用,正式上线每次都应该获取最新token,避免过期报错;

3. access_token过期时间

  • token过期时间是10分钟
  • 每次请求获取手机号接口时,都应该从客户端获取最新的token
  • 在取号成功时进行扣费,获取token不计费

4. 预登录有效期

预登录有效期为10分钟,超过10分钟后预登录失效,此时调用login授权登录相当于之前没有调用过预登录,大概需要等待1-2秒才能弹出授权界面。 预登录只能使用一次,调用login弹出授权界面后,如果用户操作取消登录授权,再次使用一键登录时需要重新调用预登录。