微信小程序登录、获取用户信息的流程及实现

发布时间 2023-04-01 11:39:22作者: 大西瓜3721

微信小程序登录、获取用户信息的流程及实现

本篇文章将通过以下三步,让你了解到小程序登录、和用户信息获取的微信生态变迁,和流程上前后端技术实现。

  1. 小程序登录流程
  2. 小程序获取手机号
  3. 小程序获取头像昵称

小程序登录

小程序登录是通过微信官方提供的登录能力, 获取微信提供的用户身份标识。通俗一点,也就是获取openIdunionId

1.登录流程

整体小程序登录可以分为两个阶段

  1. 第一阶段:是小程序 & 开发服务器 & 微信服务器三端交互,获取微信的相关登录凭证。

  2. 第二阶段:小程序 & 开发服务器交互,获取自定义的登录态,如cookie, 或者JWT等。

2.小程序登录流程

小程序登录流程图小程序登录流程图

图中的数据说明:

code: 当前用户的临时登录凭证code

session_key: 会话密钥,是对用户数据加密签名的密钥。用于服务端解析微信加密回传的用户数据。用于解析前端通过小程序 api 获取的encryptedData

从流程图中我们可以看出,因为前端只调用了wx.login这个 api,且这个 api 的调用不要用户授权,所以其实在不依赖于其他账号体系的情况下,当前这种流程就可以获取用户微信的登录状态。而用户也是无感知的(没有授权弹窗弹起)。

3.代码实现

下面,我们从代码层面实现一下获取openid的流程(代码技术栈采用,前端wepy,后端nestjs,只简单实现核心流程。)

// 前端代码
<button @tap="handleWxLogin">openid:wxLogin</button>

...

handleWxLogin() {
  wx.login({
    success(res) {
      if (res.code) {
        // 发送code临时登录凭证到后端
        wx.request({
          url: 'http://localhost:3000/wxLogin',
          data: {
            code: res.code
          }
        });
      } else {
        console.log('登录失败!' + res.errMsg);
      }
    }
  });
}

// 后端代码
async wxLogin(code) {
    // 这里我们请求了微信服务器登录凭证校验接口,
    const res = await axios.get('https://api.weixin.qq.com/sns/jscode2session', {
      params: {
        appid: this.appid,
        secret: this.secret,
        js_code: code,
        grant_type: 'authorization_code',
      },
    });
    return res.data.openid;
}

// jscode2session接口返回示例如下
{
 "openid":"xxxxxx",
 "session_key":"xxxxx",
 "unionid":"xxxxx",
 "errcode":0,
 "errmsg":"xxxxx"
}

4.效果展示

我们从下面的 gif 中看一下效果,后端正常返回了openid

5.市面上常见的两种登录形态:

我们在使用小程序的时候,经常会遇到一下两种登录情况:

两种登录形式两种登录形式

在上面说登录的时候,有说到用户不用授权也可以做到登录。但其实实际的业务场景中,只有openidunionid是不能完全满足小程序使用的。

左边的小程序,它需要获取手机号,来关联用户在 58 平台上的账号体系,所以这种情况下,会用到手机号登录。

右边的小程序,它虽然不用关联平台数据,但是在使用场景中,还是需要用户头像和昵称来给用户一个清晰的认知,告诉用户已经登录成功,如果直接放一个openid串,或者使用随机昵称,体验上会很不友好。

那么接下来我们就来看一下,在小程序中怎么获取手机号和微信头像昵称的。

小程序获取手机号

获取手机号有两种方式:

第一种

1.流程描述

前端通过wx.login获取用户的session_key, 并通过button组件,open-type=getPhoneNumber获取加密数据传给后端,后端通过解密,获取用户数据。

我们传给后端的加密数据,就是下图标注的部分:

获取手机号获取手机号
2.代码实现

我们从代码层面看一下具体的实现:

// 前端代码
<button open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">电话号码:解析加密数据</button>

...

getPhoneNumber(e) {
  wx.request({
    url: 'http://localhost:3000/getUserProfile',
    data: {
      encryptedData: e.$wx.detail.encryptedData,
      iv: e.$wx.detail.iv
    }
  });
}

// 后端代码
async getUserProfile(encryptedData, iv) {
    // 根据小程序appid、当前登录用户的sessionKey、前端获取的加密数据encryptedData、和向量iv解密获得手机号
    const pc = new WXBizDataCrypt(this.appid, this.sessionKey);

    const data = pc.decryptData(encryptedData, iv);

    return data;
}

// 解密方法
function WXBizDataCrypt(appId, sessionKey) {
  this.appId = appId;
  this.sessionKey = sessionKey;
}

WXBizDataCrypt.prototype.decryptData = function (encryptedData, iv) {
  // base64 decode
  const sessionKey = Buffer.from(this.sessionKey, 'base64');
  encryptedData = Buffer.from(encryptedData, 'base64');
  iv = Buffer.from(iv, 'base64');

  try {
    // 解密
    const crypto = require('crypto');
    const decipher = crypto.createDecipheriv('aes-128-cbc', sessionKey, iv);
    decipher.setAutoPadding(true);
    var decoded = decipher.update(encryptedData, 'binary', 'utf8');
    decoded += decipher.final('utf8');

    decoded = JSON.parse(decoded);
  } catch (err) {
    throw new Error('Illegal Buffer');
  }

  return decoded;
};

module.exports = WXBizDataCrypt;

3.效果展示

我们从下面的 gif 中看一下效果,成功取到了手机号。

重要:因为解密时需要用到登录时获取的sessionKey,所以这个方法一定要在wx.login服务端获取了session_key的情况才能使用。

第二种

1.流程描述

前端通过button组件,open-type=getPhoneNumber获取code传给后端,后端通过调用凭据(access_token)和 code 去微信服务器请求。

我们传给后端的 code 如下图标注:

获取手机号2获取手机号2
2.代码实现

我们从代码层面看一下具体的实现:

// 前端代码
<button open-type="getPhoneNumber" @getphonenumber="getPhoneNumberByCode">电话号码:code换取</button>

...

getPhoneNumberByCode(e) {
  wx.request({
    url: 'http://localhost:3000/getUserProfileByCode',
    data: {
      code: e.$wx.detail.code
    }
  });
}

// 后端代码
async getUserProfileByCode(code) {
    // 首先获取服务端与微信服务器交互的 接口调用凭证
    if (!this.accessToken) {
      const accessTokenRes = await axios.get(
        `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${this.appid}&secret=${this.secret}`,
      );
      this.accessToken = accessTokenRes.data.access_token;
    }

    // 然后根据前端获取的code去微信服务器做解析
    const res = (await axios.post(
      `https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=${this.accessToken}`,
      {
        code,
      },
    )) as any;

    return res.data.phone_info;
}

// 正常返回数据结构如下
{
    "errcode":0,
    "errmsg":"ok",
    "phone_info": {
        "phoneNumber":"xxxxxx",
        "purePhoneNumber": "xxxxxx",
        "countryCode": 86,
        "watermark": {
            "timestamp": 1637744274,
            "appid": "xxxx"
        }
    }
}
3.效果展示

我们从下面的 gif 中看一下效果,成功取到了手机号。

小程序获取头像昵称

小程序获取头像昵称的方案,可以分为三个阶段

1、通过 getUserInfo 获取。

2、回收 getUserInfo,通过 getUserProfile 获取[1]。

从微信公告中我们可以看到,因为getUserInfo会记录之前授权的状态,导致如果用户点击过不允许,那小程序后续就不会再调起微信授权,导致用户使用流程中断。所以提供了getUserProfile方法,每一次都会调起授权。下图为两个方法的流程图:

getUserInfo回收之后,接口不需要再需要用户授权,统一返回昵称为微信用户,头像为灰色头像。

**3、回收 getUserProfile,「头像昵称填写能力」获取[2]**。

近期(2022 年 11 月 8 日)微信对于获取用户信息做了第二次改动,回收了getUserProfile接口,同样返回匿名数据,获取昵称和头像分别提供了新的交互形式。

但因为微信头像昵称填写能力还有 bug,所以getUserProfile的获取用户信息方法,在小程序 sdk2.27.1 以下(微信版本 8.0.28)仍然生效。

  • 下面我们看一下原有的这两个接口的现在获取用户数据情况:
  • 我们再看一下,小程序 sdk2.27.1 一下,getUserProfile的返回数据:
  • 最后我们看一下「头像昵称填写能力」获取的方式:

以上获取用户数据的方式代码如下:

<button @tap="getUserInfo">getUserInfo_获取用户信息</button>
<button @tap="getUserProfile">getUserProfile_获取用户信息</button>
<button open-type="chooseAvatar" @chooseavatar="getUserAvatar">获取用户头像</button>
<image class="avatar" src="{{avatarUrl}}" />
<input type="nickname" placeholder="请输入昵称"/>

以上就是微信登录及获取用户信息的现有流程和实现。

作者介绍

于鹏:LBG 脚腕子前端工程师