小程序登錄流程梳理

但願經過小程序+node來總體的瞭解下小程序登錄的流程。若有不對歡迎在評論區指出👏html

1. client: wx.login / wx.getUserInfonode

微信開放平臺有APP PC 網站想用微信登陸/分享到朋友圈等其提供微信登陸、分享、支付等相關權限和服務。須要用unionid來區分用戶的惟一性。 須要經過wx.getUserInfo()api拿到用戶的encryptedData, iv這些加密數據傳給服務端,經過服務端解密獲取用戶的unionid數據庫

getUserInfo() {
  const _this = this
  wx.login({
    success(res) {
      const { code } = res
      console.log(res)
      wx.getUserInfo({
        withCredentials: true,
        success(res) {
          const { encryptedData, iv } = res
          _this.reSaveUserLogin({ code, data: encryptedData, iv })
        }
      })
    }
  })
},
複製代碼

這裏須要注意的是,wx.getUserInfo()觸發必須得經過button open-type設置爲"getUserInfo"點擊後才能觸發。json

<button wx:if="!isLogin" open-type="getUserInfo" bindtap="getUserInfo">獲取用戶信息</button>
複製代碼

2. service: request 請求微信服務 / 解密出unionid小程序

  • 服務端請求,須要的參數(js_code:client傳的code;appid:小程序惟一標識申請帳號時拿到;secret:小程序密鑰申請帳號時拿到;grant_type:默認值爲 authorization_code)
  • 獲取 openid sessen_key 參數
// 請求方法
const request = require('request')
const url = https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
module.exports = {
 async getSession(code) {
   return new Promise((resolve, reject) => {
     request(url,{
         method: 'GET',
         json: true
       },
       (error, res, body) => {
         if (error) {
           reject(error)
         } else {
           if (body.errcode) {
             reject(new Error(body.errmsg))
           } else {
             resolve(body)
           }
         }
       }
     )
   })
 }
}
複製代碼
  • 經過 appId , code,encryptedData,iv 來解密出用戶的信息其中包括 unionid字段
  • 須要注意⚠️的是,若是小程序沒有綁定過微信開放平臺解密出來的字段中是沒有unionid的
async function login(ctx) {
 const { code, data, iv } = ctx.request.body
 const session = await getSession(code)
 const { session_key, openid } = session
 const pc = new WXBizDataCrypt(appId, session_key)
 const result = pc.decryptData(data, iv)
 console.log('解密後', result)
}
複製代碼
  • 關於解密的官方提供demo WXBizDataCrypt就是直接copy的官方demo中使用node做爲服務端的模版。去官網能夠點擊下載使用便可

3.service:服務端獲取sesson_key 等信息不能直接返回客戶端須要加密解密處理api

const crypto = require('crypto')
const secret = '2019_06'
const algorithm = 'aes-256-cbc'

function encode(id) {
  const encoder = crypto.createCipher(algorithm, secret)
  const str = [id, Date.now(), '2019'].join('|')
  let encrypted = encoder.update(str, 'utf8', 'hex')
  encrypted += encoder.final('hex')
  return encrypted
}

function decode(str) {
  const decoder = crypto.createDecipher(algorithm, secret)
  let decoded = decoder.update(str, 'hex', 'utf8')
  decoded += decoder.final('utf8')
  const arr = decoded.split('|')
  return {
    id: arr[0],
    timespan: parseInt(arr[1])
  }
}
module.exports = {
  encode,
  decode
}

複製代碼

4.service:返回登錄態數組

const { encode } = require('./lib/crypto')
    const jsonMine = 'application/json'
    const now = Date.now()
    function handle(ctx, data, code = 0, message = 'success') {
      ctx.type = jsonMine
      ctx.body = {
        code,
        data,
        message
      }
    }
  router.get('/login', async (ctx, next) => {
    const { code } = ctx.request.query
    const session = await login(code)
    if (session) {
        const { session_key, openid } = session
        // 查找數據庫中是否已經存有openid,若是 hasOpenid 爲null說明是新用戶
        const hasOpenid = await User.findByPk(openid)
        if(!hasOpenid){
            // 數據庫存儲openid,時間戳
            User.create({openid,timespan:Date.now()})
        }
        handle(ctx, { token: encode(openid) })
    } else {
      throw new Error('登錄失敗')
    }
  })
複製代碼

5.client:存儲登錄態在storagebash

import { LOGIN_TOKEN } from '../../utils/localStorage'
// 拿到token存儲到客戶端
wx.setStorageSync(LOGIN_TOKEN, token)
複製代碼

我在發起請求時將登錄態放在請求頭中,相應的服務端能夠從請求頭中獲取微信

header: {
    'x-session': wx.getStorageSync(LOGIN_TOKEN)
  },
複製代碼

6.service:校驗登錄態session

module.exports = async function(ctx, next) {
  const sessionKey = ctx.get('x-session')
  const { id, timespan } = decode(sessionKey)
  // 查找數據庫中是否存在該 openid,返回是一個數組,若是不存在則返回[]
  const targetList = await getOpenid(id)
  if (targetList.length > 0) {
    // 若是超過設定的過時時間,標記isExpired字段爲登錄過時
    const oneHour = 1000 * 60 * 60 * 24
    if (Date.now() - timespan > oneHour) {
      ctx.state.isExpired = true
      // 跟前臺約定,若是code=2說明登錄過時跳登錄頁面
      handle(ctx, '', 2, '登錄過時')
    } else {
      handle(ctx, '', 0, '登錄成功')
    }
  } else {
    // 經過ctx.throw能夠直接拋出錯誤
    ctx.throw(401, '登錄失敗')
  }
複製代碼

總體流程圖

相關文章
相關標籤/搜索