概述
uniapp授权登录
微信小程序授权登录
参考:
支付宝小程序授权登录
获取手机号
准备工作:配置获取手机号权限
获取手机号,需要先到支付宝小程序后台开通获取手机号的功能。
小程序端获取加密串
小程序端获取加密串具体流程如下。
将button的open-type设置为getAuthorize;scope设置为phoneNumber,然后绑定onGetAuthorize事件:
<buttonopen-type="getAuthorize"scope='phoneNumber'onGetAuthorize="onGetAuthorize"onError="onAuthError">授权手机号</button>
在onGetAuthorize方法中调用my.getPhoneNumber方法获取手机号加密数据:
onGetAuthorize() {my.getPhoneNumber({success: (res) => {let encryptedData = res.response;console.log(encryptedData);// TODO: 请求后端接口获取解密数据},fail: (res) => {console.log('getPhoneNumber_fail');console.log(res);},});},onAuthError(e) {console.log(JSON.stringify(e.detail, '', 2));},
点击后,触发获取手机号授权弹窗: 
如果用户点击“同意”按钮,触发onGetAuthorize事件,encryptedData格式如下:
{"response":"Tl0z1mGJaPTsMQL4WfBs2Vh0TQnpJ28KQb291vOuwS/+mMz7MvpFIkDHP2r78X7gYue5YhoPOFbtwfN7hIFIpg==","sign":"pVfhUpYcVdCyLSrV4d+7GXSlgbBJdbLOOprOOx97fnGuqwAMATr5laXcQPNFMlix+4epPoqhlORS9uVFifwAvhP36Wa8aaUNJESBzbKNBA1rp9wxdXup81sdqLg2X4cYAGgdMrL2Ad57icO8/8RMGq93I0uOvfBIIx4K6MTv9OfiALL+OczsQlK7CMvjgI9v/Q2rhkXQxm6zDLsanyGXXAlKa+03ihuF36wR6mIhk/VQ8SBLF81pjFk6DFw3tQoE7TfCgJf5/hpf5gaPyZ0hQDHFqCe6T4NeFCvc9qTd9kAdJ+z+yaYrk1sV+87PZNjUDoZw5HI26mOtAvqzCIC1jA=="}
如果用户点击“拒绝”按钮,触发onAuthError事件:
{"errorMessage": "用户取消授权","type": "getAuthorize"}
参考:
- 会员能力:获取会员手机号产品介绍
- 组件:button按钮 - open-type 有效值
- 小程序API:my.getPhoneNumber
- 内容加密指引
- 用户授权
- 服务端SDK下载
- 蚂蚁金服开放平台 node sdk
服务端解析加密数据
报错:“无效的授权关系”
需要绑定button的onGetAuthorize事件,而不是onTap事件。
因为onGetAuthorize事件会在弹出授权手机号弹窗点击“同意”按钮触发;而onTap事件在点击按钮的是否就会触发,这将导致用户还没点击“同意”就已经拿到了加密串,解析此加密串会报“无效的授权关系”错误。
获取用户信息
小程序端获取加密串
要获取用户信息,需要先获取授权码code,必须用户手动触发,将其绑定到按钮的onTap中即可:
<button type="primary" onTap="getAuthCode">获取授权码</button>
在事件绑定函数中使用my.getAuthCode,并将scopes设置为auth_user,可以为数组,也可为单个字符串:
getAuthCode() {my.getAuthCode({scopes: ['auth_user'],success: (res) => {console.log(res.authCode);},});}
当点击按钮的时候,会弹出授权弹框:
当点击“同意”按钮后,可以获取到授权码code,服务器端通过解析此code获取用户信息。
c489f67044824c07ac15a4ca30b8NX83
服务端解析加密数据
头条小程序授权登录
获取手机号
准备工作:配置获取手机号权限
获取手机号,需要先到字节跳动小程序后台开通获取手机号的功能。但是需要先发布小程序成功才能开通。
注意开通获取手机号权限的限制:
- 小程序主体信用分大于 90 分;且所属主体半年内,没有严重的违规记录;
- 小程序内使用获取用户手机号的场景,需满足平台要求
- 小程序开发者需用当前小程序真实的内容进行提交
- 若开通后,发现开发者在使用过程中,滥用此能力对用户或平台造成负面影响,平台将有权利随时对该功能进行收回,并视情况对违规的小程序进行处罚。
- 个人主体小程序和未上线的小程序均不支持申请
小程序端获取加密串
小程序端获取加密串具体流程如下。
将button的open-type设置为getPhoneNumber;然后绑定bindgetphonenumber事件:
<buttonopen-type="getPhoneNumber"bindgetphonenumber="getPhoneNumberHandler">授权手机号</button>
在事件对象中可以获取到以下信息:
Page({getPhoneNumberHandler(e) {console.log(e.detail.errMsg);console.log(e.detail.iv);console.log(e.detail.encryptedData);// TODO: 后台解析手机号},});
参数说明:
| errMsg | string | 错误信息 |
|---|---|---|
| encryptedData | string | 包括敏感数据在内的完整用户信息的加密数据 |
| iv | string | 加密算法的初始向量 |
参考:
服务端解析加密数据
获取用户信息
小程序端获取加密串
获取用户信息必须用户手动触发,需要绑定button的点击事件。
<button bindtap="getUserInfo">获取用户信息</button>
getUserInfo() {tt.login({force: true,success: code => {console.log(`login:`, code)tt.getUserInfo({withCredentials: true, // 是否需要返回敏感数据success(info) {console.log('getUserInfo:', info)// 调用后端接口解析用户信息// TODO...}})},fail(err) {console.log('login 调用失败', err)}})}
返回的数据:
login返回的数据示例:
{"anonymousCode": "-N_MlQXZT47***UejN7DWZV1OgQovfSWo","code": "U0u1S7ajp***X1wDkwSz7f0","isLogin": true,"errMsg": "login:ok"}
其中,code临时登录凭证, 有效期 5 分钟。开发者可以通过在服务器端调用 登录凭证校验接口 换取 openid 和 session_key 等信息。
getUserInfo返回的数据示例:
{"userInfo": {"nickName": "xiaoyulive","avatarUrl": "https://p26-passport.byteacctimg.com/img/mosaic-legacy/3795/30***2272~300x300.image","gender": 0,"city": "","province": "","country": "中国","language": ""},"rawData": "{\"nickName\":\"xiaoyulive\",\"avatarUrl\":\"https://p26-passport.byteacctimg.com/img/mosaic-legacy/3795/30***2272~300x300.image\",\"gender\":0,\"city\":\"\",\"province\":\"\",\"country\":\"中国\",\"language\":\"\"}","signature": "16c86d310d8***46fb6608ed5","encryptedData": "TphrlfyLEdfXMZvKkEkuaugP***Bvebhq0sgy7TtF4v+R37TAZcfQkwjgv5jvmw=","iv": "p4DhlAJ***RSmCA==","errMsg": "getUserInfo:ok"}
可以看到,其实基础信息已经通过tt.getUserInfo获取到了,如果需要敏感数据,就需要后端解析encryptedData了。
参考:
服务端解析加密数据
以node.js解析加密信息为例。
服务端通过express开启HTTP服务,通过crypto解密数据,通过node-fetch请求接口:
const crypto = require("crypto")const fetch = require('node-fetch')const express = require("express")const app = express();// 解密信息function decrypt({ sessionKey, encryptedData, iv }) {const decipher = crypto.createDecipheriv("aes-128-cbc",Buffer.from(sessionKey, "base64"),Buffer.from(iv, "base64"));let ret = decipher.update(encryptedData, "base64");ret += decipher.final();return ret;}// 获取sessionKeyasync function getSession({ appid, secret, code, anonymous_code }) {let url = 'https://developer.toutiao.com/api/apps/v2/jscode2session'let request = {headers: {'Content-Type': 'application/json'},method: 'POST',body: JSON.stringify({appid,secret,code,anonymous_code})}let res = await fetch(url, request)let dataJson = await res.json()return dataJson};(() => {app.get('/', (req, res, next) => {console.log(req.query)let { code, anonymousCode, encryptedData, iv } = req.querygetSession({appid: 'tt01***d6e01',secret: '50890***ff09a7bcb',code,anonymous_code: anonymousCode,}).then(ret => {let sessionKey = ret.data.session_keylet info = decrypt({sessionKey,encryptedData,iv})res.send(info)})})app.listen(3000, function () {console.log("server start in http://127.0.0.1:3000");});})()
小程序端获取用户信息:
tt.login({force: true,success: code => {console.log(`login:`, JSON.stringify(code, '', 2))tt.getUserInfo({withCredentials: true,success(info) {console.log('getUserInfo:', JSON.stringify(info, '', 2))tt.request({url: `http://127.0.0.1:3000?code=${encodeURIComponent(code.code)}&anonymousCode=${encodeURIComponent(code.anonymousCode)}&encryptedData=${encodeURIComponent(info.encryptedData)}&iv=${encodeURIComponent(info.iv)}`, // 目标服务器urlsuccess: (res) => {console.log(res);},fail: (err) => {console.log(err);},});}})},fail(err) {console.log('login 调用失败', err)}})
获取到的sessionKey示例:
{err_no: 0,err_tips: 'success',data: {session_key: 'p9BQ3no***R/jC1pW+8g==',openid: '30c4693f-***-c8ddec9b303a',anonymous_openid: 'm-AJmGWuL.pvBM0H',unionid: 'e98a5258-***-76da3ca13424',dopenid: ''}}
解析后的用户信息:
{"nickName": "xiaoyulive","avatarUrl": "https://p26-passport.byteacctimg.com/img/mosaic-legacy/3795/303***72~300x300.image","gender": 0,"city": "","province": "","country": "中国","language": "","openId": "30c4***c9b303a","unionId": "e98a525***da3ca13424","watermark": {"appid": "tt0***e01","timestamp": 1634008408}}
报错示例:
{err_no: 40018,err_tips: 'bad code',data: {session_key: '',openid: '',anonymous_openid: '',unionid: '',dopenid: ''}}
错误码的对应关系如下:
| 错误码 | 描述 |
|---|---|
| 0 | 请求成功 |
| -1 | 系统错误 |
| 40014 | 未传必要参数,请检查 |
| 40015 | appid 错误 |
| 40017 | secret 错误 |
| 40018 | code 错误 |
| 40019 | acode 错误 |
| 其它 | 参数为空 |
参考:
