使用场景介绍
微信小程序的进入形式决定了它更适合充当一种“工具”的角色,适合在用户想起它、要用它的时候主动启动。基于此想法,__中大猫谱__小程序主要服务于猫猫信息记录、搜索的功能,旨在帮助改善校园流浪猫的生存状况、帮助大众了解猫猫及救助知识,同时帮助动保组织管理猫猫资料、募集救助(绝育、治疗)流浪猫所需的资金,为生态环境保护献一份力。
(— 广告时间结束 —)
如我们所知,微信小程序对于所有用户来说,入口都是统一的。目前,官方还没有提供配置管理员id的操作。那么,如果要将一些管理页面整合到小程序中,鉴权操作是非常必要的。例如,在中大猫谱中,普通用户和管理员用户看到的页面元素、能进入的页面是不同的:
基于这种需求,本文将分享一种在微信·云开发环境下,实现简单、安全的后台鉴权的思路。
云函数实现
在描述方法前,我们先简单介绍一下微信·云开发的三大部件,分别是做啥用的:
- 云函数:后端的自定义的业务逻辑函数,自带Node.js环境。可以被小程序端调用,通常用于操作云存储和云数据库。拥有__微信私有协议天然鉴权__(后文重点使用)。
- 存储:类似云盘,存文件用的,自带CDN(流量不多)。它提供的
cloud://
协议文件链接可以直接用于部分小程序端tag上,比如image的src,但也就不会被缓存。 - 数据库:一个文档型数据库,可以被小程序端直接使用。权限设置比较迷(为了安全),大部分__修改、删除操作__要经过云函数来调用。
我们重点关注云函数。开通云开发后,新建一个云函数,我们会得到一个初始模板:
// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init()
// 云函数入口函数
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
return {
event,
openid: wxContext.OPENID, // 调用者的openid,与每个微信号一一对应,用于鉴权
appid: wxContext.APPID,
unionid: wxContext.UNIONID,
}
}
其中,云函数接受两个参数,event
是用户调用时提供的data(可理解为形参),context
是此处调用的调用信息和运行状态。幸运的是,这两个东西我们暂时都用不到,不理解也没有关系。
我们关注函数体的第一句cloud.getWXContext()
,它正是前文提到的__微信私有协议天然鉴权__。换人话说,这个函数返回的所有资料,都是有微信背书的、可信的。那么,最简单的鉴权方式,就是把管理员的openid硬编码到云函数中,对比一下就完成了:
// isManager云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init()
// 硬编码管理员的openid
const MANAGERS = ['aaaaaaa', 'bbbbbbb', 'cccccccc'];
// 云函数入口函数
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext();
const openid = wxContext.OPENID; // 取得当前用户openid
return MANAGERS.includes(openid); // 完成鉴权
}
灵活一些
假如说我们的管理员常年不变,那上面那种鉴权方式完全够用了。但如果我们的管理员经常增删,那就利用__云存储__功能,建立一个manager
集合(collection),把管理员的openid存放到文档中(听不明白请看下图)。那么上面硬编码的部分,就改为数据库查询:
// isManager云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init()
// 初始化数据库链接
const db = cloud.database();
const _ = db.command;
// 云函数入口函数
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext();
const openid = wxContext.OPENID;
// 查询条件
const filter = { openid: openid };
// 等待数据库查询
const count = (await db.collection('manager').where(filter).count()).total;
// 检查是不是管理员
return (count === 1);
}
现在管理管理员的方式(没打错字)更灵活了,可以直接打开小程序__云开发控制台__来添加/删除一个管理员。
小程序端调用
在__小程序端__,我们可以简单包装一个鉴权函数,用callback的方式灵活地用在各种需要鉴权的地方:
function checkManager(callback) {
// 这里的callback将接受一个参数res,
// 为bool类型,当前用户为manager时为true,
// 否则为false
// 调用云函数
wx.cloud.callFunction({
name: 'isManager', // 指明云函数的名字,没有形参
}).then(res => {
// res是云函数的返回值,形如:
// {errMsg: "cloud.callFunction:ok", result: true, requestID: "44b0xxxx-8xxx-xxxx-xxxx-xxxx0068xxxx"}
// 其中的result字段是我们所需要的鉴权结果
callback(res.result);
});
}
给定不同的回调函数,就可以简单地完成各种权限控制任务。例如,在某个Page的onLoad
中控制元素显示:
const that = this;
checkManager(res => {
if (res) {
that.setData({
showBtns: true
});
}
})
上面的鉴权操作,对非管理员用户的影响几乎没有。假如普通用户确实“闯进”了不该进入的页面,也可以简单地在上面加个else分支显示提示。
安全性分析
有了云函数的啥啥啥背书(微信私有协议天然鉴权),我们不用担心用户伪造openid的问题。但是,如果有中间人篡改了服务器发来的消息(说的就是你Mallory),小程序端获得的鉴权结果已经被修改了,那这种鉴权也不太可靠。因此,在一些关键的操作(例如数据库修改)上,鉴权和实际操作都应该放在后端云函数中,一条龙完成。
幸运的是,我们上面实现的isManager
云函数仍可发光发热,被其他云函数调用。但是,__间接调用__的云函数无法获取调用者的OPENID(因为调用者不是一个微信用户,而是另一个云函数),我们要对上面的isManager
函数进行一点点修改:
// isManager云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init()
// 初始化数据库链接
const db = cloud.database();
const _ = db.command;
// 云函数入口函数
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext();
const openid = wxContext.OPENID || event.openid; // !! 只修改了这里:如果不能获取openid,就从参数event里取
// 查询条件
const filter = { openid: openid };
// 等待数据库查询
const count = (await db.collection('manager').where(filter).count()).total;
// 检查是不是管理员
return (count === 1);
}
其中修改只有一行:const openid = wxContext.OPENID || event.openid;
,这意味着如果cloud.getWXContext()
函数返回的结果里不包含openid,那就从调用时给的参数event
里取(说明当前调用是由其他云函数发起的)。
稍做修改后,在__其他云函数__中,只需在函数体最前面,增加以下几行,即可完成后端的安全鉴权:
////// 省略一些初始化代码 //////
// 其他需要后端验证的云函数入口
exports.main = async (event, context) => {
// 获取调用者的openid
const wxContext = cloud.getWXContext();
const openid = wxContext.OPENID;
// 用data参数的形式,把openid传给isManager云函数
const isManager = (await cloud.callFunction({ name: 'isManager', data: { openid: openid }}));
// 如果不是管理员,直接886
if (!isManager.result) {
return { msg: 'not a manager', result: isManager };
}
////// 下面放这个云函数要做的业务逻辑 //////
}
小总结
本文介绍了在微信小程序云开发环境下,利用云函数中微信背书的openid进行用户鉴权的一种思路。有了管理员鉴权接口、小程序端可以控制页面元素展示、路由访问、接口调用等情况,保护数据安全。目前小程序云开发的资料较少,本文也只是一种简单鉴权的实现思路,肯定还存在许多不足,希望大家多多交流、多多指正!
广告时间
今年上半年,中山大学四校区合拍的__中大猫谱__小程序正式开机,我们将与你一起继续扮演云吸猫爱好者、校园猫猫摄影大师、环境保护热心人士,希望大家多多关注,多多支持!
小程序演示视频请戳:https://v.qq.com/x/page/u0882tecle7.html