征文大学篇 - 云开发·后端鉴权思路分享
发布于 5 年前 作者 changxiulan 2016 次浏览 来自 分享

使用场景介绍

微信小程序的进入形式决定了它更适合充当一种“工具”的角色,适合在用户想起它、要用它的时候主动启动。基于此想法,__中大猫谱__小程序主要服务于猫猫信息记录、搜索的功能,旨在帮助改善校园流浪猫的生存状况、帮助大众了解猫猫及救助知识,同时帮助动保组织管理猫猫资料、募集救助(绝育、治疗)流浪猫所需的资金,为生态环境保护献一份力。

(— 广告时间结束 —)

如我们所知,微信小程序对于所有用户来说,入口都是统一的。目前,官方还没有提供配置管理员id的操作。那么,如果要将一些管理页面整合到小程序中,鉴权操作是非常必要的。例如,在中大猫谱中,普通用户和管理员用户看到的页面元素、能进入的页面是不同的:

基于这种需求,本文将分享一种在微信·云开发环境下,实现简单、安全的后台鉴权的思路。

云函数实现

在描述方法前,我们先简单介绍一下微信·云开发的三大部件,分别是做啥用的:

  1. 云函数:后端的自定义的业务逻辑函数,自带Node.js环境。可以被小程序端调用,通常用于操作云存储和云数据库。拥有__微信私有协议天然鉴权__(后文重点使用)。
  2. 存储:类似云盘,存文件用的,自带CDN(流量不多)。它提供的cloud://协议文件链接可以直接用于部分小程序端tag上,比如image的src,但也就不会被缓存。
  3. 数据库:一个文档型数据库,可以被小程序端直接使用。权限设置比较迷(为了安全),大部分__修改、删除操作__要经过云函数来调用。

我们重点关注云函数。开通云开发后,新建一个云函数,我们会得到一个初始模板:

// 云函数入口文件
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

回到顶部