概述

uniapp授权登录

微信小程序授权登录

参考:

支付宝小程序授权登录

获取手机号

准备工作:配置获取手机号权限

获取手机号,需要先到支付宝小程序后台开通获取手机号的功能。
Snipaste_2021-10-14_10-29-58.png

小程序端获取加密串

小程序端获取加密串具体流程如下。
将button的open-type设置为getAuthorize;scope设置为phoneNumber,然后绑定onGetAuthorize事件:

  1. <button
  2. open-type="getAuthorize"
  3. scope='phoneNumber'
  4. onGetAuthorize="onGetAuthorize"
  5. onError="onAuthError"
  6. >授权手机号</button>

onGetAuthorize方法中调用my.getPhoneNumber方法获取手机号加密数据:

  1. onGetAuthorize() {
  2. my.getPhoneNumber({
  3. success: (res) => {
  4. let encryptedData = res.response;
  5. console.log(encryptedData);
  6. // TODO: 请求后端接口获取解密数据
  7. },
  8. fail: (res) => {
  9. console.log('getPhoneNumber_fail');
  10. console.log(res);
  11. },
  12. });
  13. },
  14. onAuthError(e) {
  15. console.log(JSON.stringify(e.detail, '', 2));
  16. },

点击后,触发获取手机号授权弹窗:
Snipaste_2021-10-14_10-42-58.png
如果用户点击“同意”按钮,触发onGetAuthorize事件,encryptedData格式如下:

  1. {"response":"Tl0z1mGJaPTsMQL4WfBs2Vh0TQnpJ28KQb291vOuwS/+mMz7MvpFIkDHP2r78X7gYue5YhoPOFbtwfN7hIFIpg==","sign":"pVfhUpYcVdCyLSrV4d+7GXSlgbBJdbLOOprOOx97fnGuqwAMATr5laXcQPNFMlix+4epPoqhlORS9uVFifwAvhP36Wa8aaUNJESBzbKNBA1rp9wxdXup81sdqLg2X4cYAGgdMrL2Ad57icO8/8RMGq93I0uOvfBIIx4K6MTv9OfiALL+OczsQlK7CMvjgI9v/Q2rhkXQxm6zDLsanyGXXAlKa+03ihuF36wR6mIhk/VQ8SBLF81pjFk6DFw3tQoE7TfCgJf5/hpf5gaPyZ0hQDHFqCe6T4NeFCvc9qTd9kAdJ+z+yaYrk1sV+87PZNjUDoZw5HI26mOtAvqzCIC1jA=="}

如果用户点击“拒绝”按钮,触发onAuthError事件:

  1. {
  2. "errorMessage": "用户取消授权",
  3. "type": "getAuthorize"
  4. }

参考:

服务端解析加密数据

报错:“无效的授权关系”

需要绑定button的onGetAuthorize事件,而不是onTap事件。

因为onGetAuthorize事件会在弹出授权手机号弹窗点击“同意”按钮触发;而onTap事件在点击按钮的是否就会触发,这将导致用户还没点击“同意”就已经拿到了加密串,解析此加密串会报“无效的授权关系”错误。
Snipaste_2021-10-14_10-42-58.png

获取用户信息

小程序端获取加密串

要获取用户信息,需要先获取授权码code,必须用户手动触发,将其绑定到按钮的onTap中即可:

  1. <button type="primary" onTap="getAuthCode">
  2. 获取授权码
  3. </button>

在事件绑定函数中使用my.getAuthCode,并将scopes设置为auth_user,可以为数组,也可为单个字符串:

  1. getAuthCode() {
  2. my.getAuthCode({
  3. scopes: ['auth_user'],
  4. success: (res) => {
  5. console.log(res.authCode);
  6. },
  7. });
  8. }

当点击按钮的时候,会弹出授权弹框:
Snipaste_2021-10-14_11-51-34.png
当点击“同意”按钮后,可以获取到授权码code,服务器端通过解析此code获取用户信息。

  1. c489f67044824c07ac15a4ca30b8NX83

服务端解析加密数据

头条小程序授权登录

获取手机号

准备工作:配置获取手机号权限

获取手机号,需要先到字节跳动小程序后台开通获取手机号的功能。但是需要先发布小程序成功才能开通。
Snipaste_2021-10-12_10-36-27.png
注意开通获取手机号权限的限制:

  • 小程序主体信用分大于 90 分;且所属主体半年内,没有严重的违规记录;
  • 小程序内使用获取用户手机号的场景,需满足平台要求
  • 小程序开发者需用当前小程序真实的内容进行提交
  • 若开通后,发现开发者在使用过程中,滥用此能力对用户或平台造成负面影响,平台将有权利随时对该功能进行收回,并视情况对违规的小程序进行处罚。
  • 个人主体小程序和未上线的小程序均不支持申请

小程序端获取加密串

小程序端获取加密串具体流程如下。
将button的open-type设置为getPhoneNumber;然后绑定bindgetphonenumber事件:

  1. <button
  2. open-type="getPhoneNumber"
  3. bindgetphonenumber="getPhoneNumberHandler"
  4. >授权手机号</button>

在事件对象中可以获取到以下信息:

  1. Page({
  2. getPhoneNumberHandler(e) {
  3. console.log(e.detail.errMsg);
  4. console.log(e.detail.iv);
  5. console.log(e.detail.encryptedData);
  6. // TODO: 后台解析手机号
  7. },
  8. });

参数说明:

errMsg string 错误信息
encryptedData string 包括敏感数据在内的完整用户信息的加密数据
iv string 加密算法的初始向量

参考:

服务端解析加密数据

获取用户信息

小程序端获取加密串

获取用户信息必须用户手动触发,需要绑定button的点击事件。

  1. <button bindtap="getUserInfo">获取用户信息</button>
  1. getUserInfo() {
  2. tt.login({
  3. force: true,
  4. success: code => {
  5. console.log(`login:`, code)
  6. tt.getUserInfo({
  7. withCredentials: true, // 是否需要返回敏感数据
  8. success(info) {
  9. console.log('getUserInfo:', info)
  10. // 调用后端接口解析用户信息
  11. // TODO...
  12. }
  13. })
  14. },
  15. fail(err) {
  16. console.log('login 调用失败', err)
  17. }
  18. })
  19. }

返回的数据:
login返回的数据示例:

  1. {
  2. "anonymousCode": "-N_MlQXZT47***UejN7DWZV1OgQovfSWo",
  3. "code": "U0u1S7ajp***X1wDkwSz7f0",
  4. "isLogin": true,
  5. "errMsg": "login:ok"
  6. }

其中,code临时登录凭证, 有效期 5 分钟。开发者可以通过在服务器端调用 登录凭证校验接口 换取 openid 和 session_key 等信息。

getUserInfo返回的数据示例:

  1. {
  2. "userInfo": {
  3. "nickName": "xiaoyulive",
  4. "avatarUrl": "https://p26-passport.byteacctimg.com/img/mosaic-legacy/3795/30***2272~300x300.image",
  5. "gender": 0,
  6. "city": "",
  7. "province": "",
  8. "country": "中国",
  9. "language": ""
  10. },
  11. "rawData": "{\"nickName\":\"xiaoyulive\",\"avatarUrl\":\"https://p26-passport.byteacctimg.com/img/mosaic-legacy/3795/30***2272~300x300.image\",\"gender\":0,\"city\":\"\",\"province\":\"\",\"country\":\"中国\",\"language\":\"\"}",
  12. "signature": "16c86d310d8***46fb6608ed5",
  13. "encryptedData": "TphrlfyLEdfXMZvKkEkuaugP***Bvebhq0sgy7TtF4v+R37TAZcfQkwjgv5jvmw=",
  14. "iv": "p4DhlAJ***RSmCA==",
  15. "errMsg": "getUserInfo:ok"
  16. }

可以看到,其实基础信息已经通过tt.getUserInfo获取到了,如果需要敏感数据,就需要后端解析encryptedData了。

参考:

服务端解析加密数据

以node.js解析加密信息为例。
服务端通过express开启HTTP服务,通过crypto解密数据,通过node-fetch请求接口:

  1. const crypto = require("crypto")
  2. const fetch = require('node-fetch')
  3. const express = require("express")
  4. const app = express();
  5. // 解密信息
  6. function decrypt({ sessionKey, encryptedData, iv }) {
  7. const decipher = crypto.createDecipheriv(
  8. "aes-128-cbc",
  9. Buffer.from(sessionKey, "base64"),
  10. Buffer.from(iv, "base64")
  11. );
  12. let ret = decipher.update(encryptedData, "base64");
  13. ret += decipher.final();
  14. return ret;
  15. }
  16. // 获取sessionKey
  17. async function getSession({ appid, secret, code, anonymous_code }) {
  18. let url = 'https://developer.toutiao.com/api/apps/v2/jscode2session'
  19. let request = {
  20. headers: {
  21. 'Content-Type': 'application/json'
  22. },
  23. method: 'POST',
  24. body: JSON.stringify({
  25. appid,
  26. secret,
  27. code,
  28. anonymous_code
  29. })
  30. }
  31. let res = await fetch(url, request)
  32. let dataJson = await res.json()
  33. return dataJson
  34. }
  35. ;(() => {
  36. app.get('/', (req, res, next) => {
  37. console.log(req.query)
  38. let { code, anonymousCode, encryptedData, iv } = req.query
  39. getSession({
  40. appid: 'tt01***d6e01',
  41. secret: '50890***ff09a7bcb',
  42. code,
  43. anonymous_code: anonymousCode,
  44. }).then(ret => {
  45. let sessionKey = ret.data.session_key
  46. let info = decrypt({
  47. sessionKey,
  48. encryptedData,
  49. iv
  50. })
  51. res.send(info)
  52. })
  53. })
  54. app.listen(3000, function () {
  55. console.log("server start in http://127.0.0.1:3000");
  56. });
  57. })()

小程序端获取用户信息:

  1. tt.login({
  2. force: true,
  3. success: code => {
  4. console.log(`login:`, JSON.stringify(code, '', 2))
  5. tt.getUserInfo({
  6. withCredentials: true,
  7. success(info) {
  8. console.log('getUserInfo:', JSON.stringify(info, '', 2))
  9. tt.request({
  10. url: `http://127.0.0.1:3000?code=${encodeURIComponent(code.code)}&anonymousCode=${encodeURIComponent(code.anonymousCode)}&encryptedData=${encodeURIComponent(info.encryptedData)}&iv=${encodeURIComponent(info.iv)}`, // 目标服务器url
  11. success: (res) => {
  12. console.log(res);
  13. },
  14. fail: (err) => {
  15. console.log(err);
  16. },
  17. });
  18. }
  19. })
  20. },
  21. fail(err) {
  22. console.log('login 调用失败', err)
  23. }
  24. })

获取到的sessionKey示例:

  1. {
  2. err_no: 0,
  3. err_tips: 'success',
  4. data: {
  5. session_key: 'p9BQ3no***R/jC1pW+8g==',
  6. openid: '30c4693f-***-c8ddec9b303a',
  7. anonymous_openid: 'm-AJmGWuL.pvBM0H',
  8. unionid: 'e98a5258-***-76da3ca13424',
  9. dopenid: ''
  10. }
  11. }

解析后的用户信息:

  1. {
  2. "nickName": "xiaoyulive",
  3. "avatarUrl": "https://p26-passport.byteacctimg.com/img/mosaic-legacy/3795/303***72~300x300.image",
  4. "gender": 0,
  5. "city": "",
  6. "province": "",
  7. "country": "中国",
  8. "language": "",
  9. "openId": "30c4***c9b303a",
  10. "unionId": "e98a525***da3ca13424",
  11. "watermark": {
  12. "appid": "tt0***e01",
  13. "timestamp": 1634008408
  14. }
  15. }

报错示例:

  1. {
  2. err_no: 40018,
  3. err_tips: 'bad code',
  4. data: {
  5. session_key: '',
  6. openid: '',
  7. anonymous_openid: '',
  8. unionid: '',
  9. dopenid: ''
  10. }
  11. }

错误码的对应关系如下:

错误码 描述
0 请求成功
-1 系统错误
40014 未传必要参数,请检查
40015 appid 错误
40017 secret 错误
40018 code 错误
40019 acode 错误
其它 参数为空

参考: