使用场景
面对有些开发时,可能微信开放平台未绑定公众号以及未开放开发者资质认证的场景时进行使用。这种情况下使用 微信小程序提供的服务端接口: GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code 无法获取到union_id
1、在服务端根据code获取道session_key,切记session_key不能返回给前端。这里提供两种方式:一种是如果是未获取过授权状态的用户,在前端定时刷新登录态,确保code码始终在有效期之内。然后在获取union_id的时候将code一同传入。另一种是将获取到的session_key存储到存入到服务端缓存当中,需要用到时就进行调用。
具体实现方法:https://blog.csdn.net/pyp_demon/article/details/108802350
2、根据获取到的session_key获取union_id(这里小程序端使用的是button 的封装属性 open-type=“getUserInfo” 进行用户登录授权,然后根据返回的加密信息encrytedData/signature/iv/rowData)。服务端代码如下:
/* eslint valid-jsdoc: "off" */
'use strict'; // config/config.default.js 配置appid和secret
/**
* @param {Egg.EggAppInfo} appInfo app info
*/
module.exports = appInfo => {
/**
* built-in config
* @type {Egg.EggAppConfig}
**/
const config = exports = {};
// 微信小程序信息配置
config.wxApp = {
one: { // pyp答题窗小程序
appid: '',
secret: '',
},
two: { // 阿龙团拼小程序
appid: '',
secret: '',
},
};
return {
...config,
};
};
'use strict'; // app/service/core/base.js
const Service = require('egg').Service;
class BaseService extends Service {
}
module.exports = BaseService;
'use strict'; // app/service/wx_app_api/login.js
const BaseService = require('../core/base');
const crypto = require('crypto');
class LoginService extends BaseService {
/**
* 获取openid
* @param {*} code 前端登录码
* @param {*} num config中配置的小程序的标识
*/
async code2Session(code, num = 'one') {
const { ctx } = this;
const wxApp = this.config.wxApp[num];
const url = 'https://api.weixin.qq.com/sns/jscode2session?appid=' + wxApp.appid + '&secret=' + wxApp.secret + '&js_code=' + code + '&grant_type=authorization_code';
const res = await ctx.curl(url, {
dataType: 'json',
});
if (res.data.openid) {
return {
openid: res.data.openid,
sta: true,
sessionKey: res.data.session_key,
};
}
return { // 忽略网络请求失败
msg: res.data.errmsg,
sta: false,
};
}
/**
* 根据微信小程序的getUserInfo返回的加密数据解析用户的openid和unionId,适用于微信开放平台未进行开发者资质认证和绑定微信公众号的时候
* @param {*} encryptedData 用户加密数据
* @param {*} iv 加密算法的初始向量
* @param {*} sessionKey 当前会话密钥
* @param {*} signature 签名
* @param {*} rawData 用户基准数据
* @param {*} num 配置的小程序编号
*/
async getUnionIdByEncry(encryptedData, iv, sessionKey, signature, rawData, num = 'one') {
const signature1 = crypto.createHash('sha1').update(rawData + sessionKey).digest('hex'); // 根据会话密钥和基准数据生成签名
if (signature !== signature1) { // 验证签名是否一致
return false;
}
const wxApp = this.config.wxApp[num]; // 调用小程序配置信息
if (sessionKey.length !== 24 || iv.length !== 24) { // 判断会话密钥和加密算法初始向量位数是否一致
return false;
}
const keyStr = Buffer.from(sessionKey, 'base64'); // seesion_key的base64解码
const ivStr = Buffer.from(iv, 'base64'); // iv的base64解码
const encryStr = Buffer.from(encryptedData, 'base64'); // encryptedData的base64解码
const decipher = crypto.createDecipheriv('aes-128-cbc', keyStr, ivStr); // 解密
decipher.setAutoPadding(true); // 设置自动 padding 为 true,删除填充补位
let decoded = decipher.update(encryStr, 'binary', 'utf8');
decoded += decipher.final('utf8');
decoded = JSON.parse(decoded);
if (decoded.watermark.appid !== wxApp.appid) {
return false;
}
return decoded;
}
}
module.exports = LoginService;
服务端controller内调用
// 获取session_key
const codeRes = await this.service.wxAppApi.login.code2Session(code);
// 获取union_id
const rs = await this.service.wxAppApi.login.getUnionIdByEncry(encrytedData, iv, codeRes.sessionKey, signature, rawData);
解密后的数据如下:
{
openId: '',
nickName: '翠竹幽幽莫箜篌',
gender: 1,
language: 'zh_CN',
city: '烟台',
province: '山东',
country: '中国',
avatarUrl: 'https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTJ2GuAvvKecNZ1DMc9WlATDwUeUFEsUrMMAWic2pFsEHBnNAia3Z1vJxNRGbe0y7zrhoDdestbq3ubQ/132',
unionId: '',
watermark: { timestamp: 1602210208, appid: '' }
}