Node.js +小程序登录详解(新)(session_key、openid、3rd_session、wx.request())

前言

此篇内容记录本人编程过程中所遇到的一些问题和解决的办法。仅供参考,有些内容引用别处。[非商用,如侵删]

正文

先看图 基于小程序官方给出的图片 经过网上查找 到一张更加详细的时序表

在这里插入图片描述

图片引自:东边的小山:微信小程序获取用户信息并保存登录状态详解

根据疑惑我把这张图中的内容拆分成一个个小的问题来解决

第一步:通过code获取openid和session_key

微信小程序端 App.js wx.login引用官方文档的示例

1)、获取code、iv、encryptedData

iv和encryptedData后面用来解密用户信息

//  小程序全局只触发一次 触发时机为初始化后执行的生命周期函数,
onLaunch(){
wx.getUserProfile({
      lang: 'zh_CN',
      desc: 'desc',
      success(res) {
        const {encryptedData,iv} = res
        wx.login({
          success(res) {
            wx.request({
              url: 'http://localhost:3000/api/user/sendcode',
              data: {
                code: res.code,
                iv,
                encryptedData,
                }
             })
          }
       })
     }
  })
}

调用wx.login()方法 将获取到的 res.code 通过 wx.request() 发送到后端 接收到数据后把数据保存起来

获取到的code
iv和encryptedData
在这里插入图片描述

2)、获取session_Key和openid

后端携带 Appid、AppSecret、code向微信服务器发起GET请求

	const { iv, code, encryptedData } = req.query// 解构赋值
    // 向微信服务器发起get请求 通过 code 换取 Session_key 和 openid
    let wxURl = `https://api.weixin.qq.com/sns/jscode2session?
                 appid=${wxAppkey.appid}
                 &secret=${wxAppkey.secret}
                 &js_code=${code}
                 &grant_type=authorization_code`
    // 保存请求结果
    const { data } = await $.get(wxURl)

至此 我们已经拿到了 session_key 和 openid 的值

3)、解密获取用户信息

官方有给出demo > 开放数据校验与解密在这里插入图片描述
node 实例

WXBizDataCrypt.js

var crypto = require('crypto')

function WXBizDataCrypt(appId, sessionKey) {
  this.appId = appId
  this.sessionKey = sessionKey
}

WXBizDataCrypt.prototype.decryptData = function (encryptedData, iv) {
  //转换为base64格式数据
  iv = Buffer.from(iv, 'base64')
  encryptedData = Buffer.from(encryptedData, 'base64')
  sessionKey = Buffer.from(this.sessionKey, 'base64')

  try {
    //根据给定的算法,密钥和初始化向量,创建并返回一个Decipher解密对象。
    var decipher = crypto.createDecipheriv('aes-128-cbc', sessionKey, iv)
    // 设置自动 padding 为 true,删除填充补位
    decipher.setAutoPadding(true)
    //往decipher实例中添加数据,第一个参数是数据,第二个参数是传入数据的格式,默认是 ‘binary’。第三个参数是返回的数据格式。
    var decoded = decipher.update(encryptedData, 'binary', 'utf8')
    //返回任何剩余的解密内容。不能调用多次
    decoded += decipher.final('utf8')
    decoded = JSON.parse(decoded)

  } catch (err) {
    throw new Error('Illegal Buffer')
  }
  //检验是否获取正确,是当前appid的数据
  if (decoded.watermark.appid !== this.appId) {
    throw new Error('Illegal Buffer')
  }

  return decoded
}

module.exports = WXBizDataCrypt

puzzle.js

const WXBizDataCrypt = require('./WXBizDataCrypt')
const { wxAppkey } = require('../../config/wxappConfig')


function getPuzzleUserinfo(sessionKey, encryptedData, iv) {
  // 创建实例将appid 和session_key传入原型中
  const pc = new WXBizDataCrypt(wxAppkey.appid, sessionKey)
  // 将传入的iv,encryptedData,和实例原型中的session_key 一起进行解密 结果返回给data
  const data = pc.decryptData(encryptedData, iv)
  return data
}

module.exports = {
  getPuzzleUserinfo
}

*了解:什么是3rd_session?(自定义登录态)

什么是登陆态:

抱着这个疑惑我去网络上找各种答案,零零散散的看了很多:

所谓登录态,就是程序在运行时,能够识别当前用户,能够证明自己的唯一性且合法。
WEB服务器通过浏览器携带的token获取session来判断是否是同一用户(或浏览器);Restful服务通过客户端传过来唯一ID,来识别调用用户

以上内容引自微信小程序登录态

希望帮助理解,我的理解:
登录态是用来校验用户在小程序端第三方服务器端的登录状态,类似与传统Web项目中的token和sseion。

什么是自定义登录态?

我的理解就是是自己生成的一个字符串 用于验证用户登录态的伪token然后我们需要为其设定失效时间并保管在缓存(我这里用的redis)中,用户每次发起请求都要携带这个伪token,然后我们判断Token是否过期决定能否请求成功,如果未过期,返回相关的数据,如果过期了则需要用户重新发起登录请求重新返回给其token值。

第二步:生成自定义登录态并传入缓存

先继续上码🐎
user.js

    if (req.method === 'GET' && req.path === '/api/user/sendcode') {
        // 向服务器发起请求获取到session_key和openid以及解密后的用户信息userinfo 解构赋值得到 (sessionKey/openid)、SQL数据模型、Res数据模型
        let { data: wxOpenidAndKey, userinfoSQLData, userinfoResData } = await getWxokAndUserinfo(req)
        // 对合并后的内容wxos进行加密 
        let { result: token, wxos: value } = sha1(wxOpenidAndKey)
        // 以Token为 Key wosx为值 存入缓存
        redis.setRedis(token,wxos)
    }

----------上面用到的方法---------------------------

//getWxOpenidAndKey()
 async function getWxokAndUserinfo(req, res) {
    const { iv, code, encryptedData } = req.query// 解构赋值
    // 向微信服务器发起get请求 通过 code 换取 Session_key 和 openid
    let wxURl = `https://api.weixin.qq.com/sns/jscode2session?appid=${wxAppkey.appid}&secret=${wxAppkey.secret}&js_code=${code}&grant_type=authorization_code`
    // 保存请求结果
    const { data } = await $.get(wxURl)
    // 如果为空,将错误抛出
    if (!data) throw '向微信服务器发起get请求错误'
    // 解构赋值出session_key和openid
    const { session_key, openid } = data
    // 通过对传回session_key、iv、encryptedData进行解密获取用户的信息
    const userinfo = getPuzzleUserinfo(session_key, encryptedData, iv)
    // 将内容openid和用户信息userinfo封装成一个数据模型 准备传回数据库
    const userinfoSQLData = userinfoSQLModels(userinfo, openid)
    const userinfoResData = userinfoResModels(userinfo)
1)、生成自定义登录态

根据官方建议是通过Linux 下的’/dev/urandom’, ‘rb’ 生成一个随机数来作为key。
但是!因为我对Linux并不了解也没有Linux的设备
所以我用了nodejs中的crpto加密模块中的加盐算法,用一个11位的随机数作为Salt 对session_key+openid进行加密,作为 伪Token
先上🐎

// 加密 生成的随机字符 
let { result: token, wxos: value } = sha1(wxOpenidAndKey)

sha1.js

const crypto = require('crypto')

// 随机生成一个11位数的随机数
let randnumber11 = Math.random().toString(36).substr(2)

//封装方法-> 将传入的值(token) 通过加盐算法加密 盐为一个随机生成的11位随机数
function sha1(wxOpenidAndKey) {
  const { openid, session_key } = wxOpenidAndKey
  // 用问号分割key和id
  const wxos = session_key + '?' + openid;
  console.log('controller', wxos);
  // 通过hamc 加盐算法 将哈希算法sha1 与一个密钥->随机生成的11位随机数作为佐料(盐)加密
  let hamc = crypto.createHmac('sha1', randnumber11)
  // 加密传入的str并返回64位16进制数的值 作为 token的 key 
  let result = hamc.update(wxos).digest('hex')
  if (!result) {
    console.log('加密失败');
  }
  return {result,wxos};
}
module.exports = {
  sha1
}

2)、将自定义登录态写入redis缓存

这里涉及到了 redis 的一些内容
我稍微看了一下文档决定先用在看之后再往深了研究

相关的内容在我的另一篇博客 Node.js: redis 和 ioredis 的基本使用

里面封装了几个基本的使用方法

//添加进缓存 key为token 值为openid 和session_key
redis.setRedis(token,wxos)

然后可以再redis的命令行中 输入

keys *

查询所有的键值对 看看是不是添加成功了

第三步:将token返回给小程序端

这里默认用户的登录状态是首次登录

1)、通过数据模型返回给前端
 return new SuccessModel(userinfoResData, token, '登录成功,Token已保存Redis')
// 数据模型
class BaseModel {
    constructor(data, token=false,message) {
        if ((typeof data) === "string") {
            this.message = data;
            token = null;
            data = null;
            message = null;
        }
        if (data) {
            this.data = data
        }
        if (message) {
            this.message = message
        }
        if (token) {
            this.token = token
        }
    }
}
// 成功时返回的数据模型
class SuccessModel extends BaseModel {
    constructor(data,token=false, message) {
        super(data, message)
        this.success = true;
        this.errno = 0;
        this.token = token;
    }
}

第四步:小程序端保存 token

小程序端接收到返回的数据后将token保存至Storage在这里插入图片描述

//key为token 值为发起请求返回的token
wx.setStorageSync('Token', res.token)

第五步:封装wx.request() 让小程序端携带 token发起请求

我这里对wx.request()方法进行了封装
1、先转化为promise形式请求
2、再转为通过async/await简化的方式请求
将storage中的token 携带再请求头里 这样每次就都能携带token发起请求了

// wx.request() 网络请求转换Promise回调形式
function wxRequest (method, url, data) {
  // 将request请求转化为Promise类型的请求
  return new Promise((reslove,reject)=>{
    const token = wx.getStorageSync('Token')
    wx.request({
      url: url,
      method: method,
      data: data,
      header: {
        'content-type': method == 'GET' ? 'application/json' : 'application/x-www-form-urlencoded',
        'Accept': 'application/json',
        'token':token
      },
      dataType: 'json',
      success: function (res) {
        reslove(res.data);
      },
      fail: function (err) {
        reject(err);
      }
    })
  })
}
// Promise回调形式请求转化为 async/await 请求
async function WxRequestSync(method, url, data) {
  const result = await wxRequest(method,url,data)
  return result
}
module.exports = {
  wxRequest,
  WxRequestSync
}

第六步:服务端收到 token分析用户登录形态

1)、通过封装的redis查询方法判断token是否过期
 
  获取请求头中的token  如果是第一次登录 则为空
  ----
  const { token: wxToken } = req.headers
  
  如果请求头中的token 不为空 就在Redis中查询 是否有key 如果结果为0 那么说明该Token已经过期
   ---
  const isRedisToken = await redis.queryRedis(wxToken)
① token过期

如果结果返回 0 那么说明token过期

1、需要重新拿用户传入的code 去向微信服务器交换session_key 和openid

let { data: wxOpenidAndKey } = await getWxokAndUserinfo(req) 

2、拿到加密后的token 和 value(openid和session_key)

// 对合并后的内容wxos进行加密 
let { result: token, wxos: value } = sha1(wxOpenidAndKey)

3、保存进redis、通过openid查询数据库中的数据 并返回

    // 将token 和value(openid和session)存进redis
    let isSetRedis = await redis.setRedis(token, value)
    // 判断是否存入成功 成功的话将token值传给前端保存在Storage中
    if (isSetRedis != 'OK') throw isSetRedis;
    //拿到openid
    let reidsOpenid = await (await redis.getRedis(token)).split('?')[1]
    let res = await queryUserinfo(reidsOpenid)
    return new SuccessModel(res, token)
② token未过期

如果结果返回 1 那么说明token未过期
执行下面的操作
1、为该 token 增加过期时间(自定)
2、查询到该token对应的值 拿到openid
3、在数据库中查询 该openid 的数据
4、 通过成功的数据模型 返回给前端

 	let redisToken = await (await redis.getRedis(wxToken)).split('?')[1]
    timeSetRedis('wxToken',3600) //增加过期时间 6个小时
    let res = await queryUserinfo(redisToken)
    if (res != '') {
      return new SuccessModel(res)
    } else {
      return new ErrorModel(res)
    }

完结撒花 此篇内容有不对的地方欢迎大佬批评指正
如果有不理解的地方也可以评论区评论一下~

  • 14
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

比苦瓜苦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值