微信小程序登录、获取用户信息的流程及实现
微信小程序之获取并解密用户数据(获取openid,nickName等)
本篇文章将通过以下三步,让你了解到小程序登录、和用户信息获取的微信生态变迁,和流程上前后端技术实现。
- 小程序登录流程
- 小程序获取手机号
- 小程序获取头像昵称
小程序登录
小程序登录是通过微信官方提供的登录能力, 获取微信提供的用户身份标识。通俗一点,也就是获取openId
, unionId
。
1.登录流程
整体小程序登录可以分为两个阶段
- 第一阶段:是小程序 & 开发服务器 & 微信服务器三端交互,获取微信的相关登录凭证。
- 第二阶段:小程序 & 开发服务器交互,获取自定义的登录态,如
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.市面上常见的两种登录形态:
我们在使用小程序的时候,经常会遇到一下两种登录情况:
两种登录形式
在上面说登录的时候,有说到用户不用授权也可以做到登录。但其实实际的业务场景中,只有openid
和unionid
是不能完全满足小程序使用的。
左边的小程序,它需要获取手机号,来关联用户在 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.代码实现
我们从代码层面看一下具体的实现:
// 前端代码
<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 脚腕子前端工程师