微信小程序登陸會話流程學習

背景

微信小程序的使用能夠快速的基於場景進行用戶圈的創建推廣,其中根據業務須要使用用戶信息以及受權過程,主要用到的有微信在開發工具中已經封裝好的 api 函數:wx.loginwx.getUserInfowx.checkSessionwx.getStorageSyncwx.requestphp


1. 關鍵詞

  • 開放數據
    調用 wx.getUserInfo(此接口有調整,使用該接口將再也不出現受權彈窗,使用 <button open-type="getUserInfo" ></button> 引導用戶主動進行受權操做) 成功時 返回的用戶開放數據,能夠在任意時候獲取,但其中的加密數據encryptedData須要使用用戶的session_key做爲祕鑰進行解密才能獲得原始完整數據。爲了防止返回的數據被人篡改僞造,開發者能夠將 signature、rawData 發送到開發者服務器進行校驗。服務器利用用戶對應的 session_key 使用相同的算法計算出簽名 signature2 ,比對 signature 與 signature2 便可校驗數據的完整性。返回結果主要包括:
參數             類型                          說明
    userInfo         OBJECT        用戶信息對象,不包含 openid 等敏感信息
    rawData          String        不包括敏感信息的原始數據字符串,用於計算簽名。
    signature        String        使用 sha1( rawData + sessionkey ) 獲得字符串,用於校驗用戶信息,參考文檔 signature。
    encryptedData    String        包括敏感數據在內的完整用戶信息的加密數據,詳細見加密數據解密算法
    iv               String        加密算法的初始向量,詳細見加密數據解密算法
//調用成功後返回結果示例
{
encryptedData:"6exNeBvACX+EpzLNF2vYRhM0Z1tNZnBOYP0Qh6jQ4Ofv+69qY/RGVAB34aj4f364mkjAAD5pgDDJ41hGkkr/IaHJGioI3EhGSEQyrAfE1mPovV9s6Prg4rKedfBUerG4jEgXbS0GeERMtJ15tqEGz7qzcA49c1D1obZTuneImZq1vB1lXo3oUbnmK7LUAxQcgCQA==",
errMsg:"getUserInfo:ok",
iv:"X6PuFoueBpfgyjV9l9D6A==",
rawData:"{"nickName":"xxx","gender":1,"language":"zh_CN","city":"","province":"","country":"Iceland","avatarUrl":"https://wx.qlogo.cn/mmsen/vi_s/0daYVYsscPNxEvALzBTsJarXwUmlxjZJZMHPM6NNLegH4wLRcRlsA1x4bsJg/132"}",
signature:"c32wba325164258b9a332b6fdsdew2e520081348",
userInfo:
    {
        avatarUrl:"https://wx.qlogo.cn/mmsen/vi_s/0daYVYsscPNxEvALzBTsJarXwUmlxjZJZMHPM6NNLegH4wLRcRlsA1x4bsJg/132",
        city:"",
        country:"Iceland",
        gender:1,
        language:"zh_CN",
        nickName:"xxx",
        province:""
     }
}
  • 敏感數據獲取(加密數據encryptedData簽名signature)
    signature.jpg?t=2018515
    對於encryptedData,開發者如須要獲取敏感數據,須要對接口返回的加密數據( encryptedData )進行對稱解密
    也就是須要在開發者服務器上利用session_key解密得到的encryptedData,其結構以下:
{
    "openId": "OPENID",
    "nickName": "NICKNAME",
    "gender": GENDER,
    "city": "CITY",
    "province": "PROVINCE",
    "country": "COUNTRY",
    "avatarUrl": "AVATARURL",
    "unionId": "UNIONID",
    "watermark":                      //數據水印
    {
        "appid":"APPID",              //敏感數據歸屬appid,開發者可校驗此參數與自身appid是否一致  
        "timestamp":TIMESTAMP         //敏感數據獲取的時間戳, 開發者能夠用於數據時效性校驗
    }
}
  • 自定義登陸態
    環境:開發者服務器上
    數據:利用接口調用得到的開放數據或者解密後的敏感數據以及session_key
    操做:利用數據創建用戶在小程序的惟一標識,以及發送本身定義的登陸狀態信息記錄session(也能夠是其餘的信息記錄)給客戶端,session_key做爲祕鑰,必須保存在開發者服務器上(之前接口https://api.weixin.qq.com/sns... 能夠在小程序開發設置--服務器配置裏設置爲合法地址,如今不行了,只能使用第三方服務器進行接口調用)
    使用: 用戶非首次登錄——>查詢本地緩存中的自定義session——>獲取到session向開發者服務器查詢——>服務器上此session未過時可用——>用戶可正常業務請求(這只是登錄狀況比較廣泛正常的一種)

2.登陸

官方給的圖示:(大概步驟能夠看官方文檔)
api-login.jpg?t=2018515
基本:其中使用wx.login()能夠得到開發者服務器向微信接口服務器請求得到session_key等數據時所須要的參數code,開發者服務器以code+appid+app secret(小程序祕鑰,管理後臺能夠得到)換取 用戶惟一標識openid 和 會話密鑰session_key。但每一次調用wx.login()都會更新微信接口服務器上的session_key。html

  • 首次登陸node

    獲取登陸憑證code——>發送至開發者服務器構造接口( appid + app secret + code) 請求 微信接口服務器數據——>返回用戶數據密鑰session_key 和惟一標識openid給開發者服務器——>根據session_key 和 openid等生成自定義的第三方session,存儲session_key至開發者服務器,不能將session_key發送至客戶端,防止敏感數據泄露——>將第三方自定義登陸信息session發送至客戶端——>保存自定義第三方登陸態信息session到本地緩存,登陸成功,能夠設置一個登陸標識flag,使flag=true表示首次登陸成功,進行相關的處理操做。
    其中,登陸校驗憑證接口(在開發者服務器中調用,小程序中沒法使用此接口)爲:c++

    https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
    
       appid          小程序惟一標識
       secret         小程序的 app secret
       js_code        是wx.login登陸時獲取的憑證code
       grant_type     填寫爲 authorization_code

    具體僞代碼以下:算法

/**
 * 我把這一步封裝成一個函數,之後再調用那些須要登陸後纔有權限訪問的後臺服務時.
 * 可使用使用這個函數操做,基本邏輯,還能夠進行擴展
 * @url      : 開發者服務器登陸服務接口 可設置默認值
 * @key      : 存入本地緩存的第三方自定義登陸session的鍵值
 * @callback : 須要用到登陸服務的回調函數(或函數集合)
 */
function  Login(key,url,callback){
     // 首先使用wx.login獲取登陸憑證code併發送給開發者服務器
     wx.login({
      success: function(res) {
        if (res.code) {
          //發送code給開發者服務器以獲取session_key和openid
          wx.request({
            url:url,
            data: {
              code: res.code
            },
            //發送至開發者服務器成功
            success: function(res) {
                //開發者服務器得到session_key和openid成功並返回了
                //第三方自定義session或者解密數據等json格式數據res給客戶端
                if(res.has3rdSession){
                    //登陸成功,將第三方session存入本地緩存
                    console.log("登陸成功")
                    wx.setStorageSync(key,res.3rdSession)
                    //設置登陸標識爲true
                    this.setData{
                        flag:true
                    }
                    //進行後續回調處理
                    callback(res)
                    ....
                }
                else{
                    //沒有返回第三方自定義session,登陸失敗
                    console.log("登陸失敗")
                    ....
                }
            }
          })
        } else {
          //沒有獲取到登陸憑證code,登陸失敗
          console.log('登陸失敗!' + res.errMsg)
          ....
        }
      }
    })
}
  • 非首次登陸

    wx.getStorageSync()查詢本地緩存中是否有咱們自定義的session登陸態信息,有的話證實不是第一次登陸,此時先查詢微信接口服務器上的用戶session_key是否過時,若是過時了,無論咱們本身定義的session如何,都要從新登陸,設置登陸標識flag爲false;若是沒有過時,那麼能夠向開發者服務器發送sessio查詢用戶信息,若是開發者服務器告訴咱們本身定義的session過時了,那從新登陸,設置登陸標識flag爲false,若是沒有過時,則查詢是否有這個session來得到用戶的相關信息,具體僞代碼以下:json

/**
 - 我把這一步封裝成一個函數,之後再調用那些須要登陸後纔有權限訪問的後臺服務時.
 - 可使用使用這個函數操做,基本邏輯,還能夠進行擴展
 - @url      : 開發者服務器服務接口
 - @key      : 本身定義的session存儲在本地緩存中的鍵名
 - @callback : 須要用到登陸服務的回調函數(或函數集合)
 - @others   : 其餘須要用到的數據等參數
 */
function doRequestWithSession(key,url,callback,others)
{
    try {
      var value = wx.getStorageSync(key)
      if (value) {
          // 存在自定義登陸session,非首次登陸
          // 查詢 用戶密鑰session_key是否過時
          wx.checkSession({
              success: function(){
                //session_key 未過時,而且在本生命週期一直有效
                //向開發者服務器發送自定義session查詢用戶信息
                wx.request({
                      url: url, 
                      data: value,  //本地緩存的自定義Session值 能夠加上其餘須要的數據others等
                      success: function(res) {
                         if(res.hasSession){
                            //查詢成功,開發者服務器上有咱們自定義的session
                            //根據返回結果進行對應的操做,先判斷session是否過時
                            //若是咱們自定義的session已通過期
                            if(res.expired){
                                console.log("session過時,從新登陸")
                                //設置登陸標識爲false
                                this.setData{
                                    flag:false
                                }
                                wx.login() //從新登陸 或者 返回錯誤碼
                                ....
                            }
                            else{
                            //沒有過時,返回的結果則能夠是用戶的相關信息
                            //進行相關操做 登陸成功
                               console.log("歡迎回來")
                               callback(res)
                               ....  
                            }    
                          }
                          else
                          {
                              //開發者服務器上沒有咱們自定義的session,從新登陸
                              console.log("無此session,從新登陸")
                              //設置登陸標識爲false
                                this.setData{
                                    flag:false
                                }
                              wx.login() //從新登陸 或者 返回錯誤碼
                              ....
                          }             
                      }
                })
              },
              fail: function(){
                // session_key 已經失效,須要從新執行登陸流程
                //設置登陸標識爲false
                this.setData{
                    flag:false
                }
                wx.login() //從新登陸 或者 返回錯誤碼
                ....
              }
          })    
      }
      else{
          //本地緩存中沒有自定義的session登陸態信息,則進行首次登陸或者後續處理
          //設置登陸標識爲false
          this.setData{
             flag:false
          }
          wx.login() //登陸 或者 返回錯誤碼
          ....
      }
    } catch (e) {
      // 異常處理
      ....
    }
 }
  • 條件判斷登陸
    須要用到登陸權限的服務時,可使用條件判斷
    僞代碼:
if(this.data.flag){
    //已經登陸過,非首次登陸
    doRequestWithSession(key,url,callback,others)
    ....
}
else{
    //沒有登陸過,能夠進一步檢查本地緩存是否有3rdSession
    do other things
    ....
}

3.獲取安全的原始數據

在第一部分已經說過敏感數據和開放數據,能夠用 wx.getUserInfo得到開放數據,可是沒有登陸獲取解密密鑰session_key是沒法解密敏感數據的,因此開放數據中,沒有登陸的話,咱們只能得到用戶暱稱、頭像、城市、性別、國籍等,並無能夠標識用戶的惟一id。因此如何拿到咱們的安全的原始數據,只有:獲取開放數據——>得到登陸權限,開發者服務器得到密鑰session_key校驗簽名解密數據——>返回解密數據,包含有openid。僞代碼:小程序

a.wxml 文件:微信小程序

//wx.getUserInfo接口有調整,使用該接口將再也不出現受權彈窗,要使用 <button>組件引導用戶主動進行受權操做,使用此接口要注意:
//當用戶未受權過,調用該接口將直接報錯;當用戶受權過,可使用該接口獲取用戶信息
//例子
<button open-type='getUserInfo' bindgetuserinfo='showUserInfo'>登陸</button>

a.js 文件:api

Page({
  data:{},
  ...
  //點擊登陸按鈕後觸發
  showUserInfo:function(e){
    //獲取須要解密的數據
    var encryptedData = e.detail.encryptedData
    /*
    *@key      第三方自定義登陸session存在本地緩存的key值
    *@url      解密敏感數據的開發者服務器服務接口
    *@callback 得到解密數據後的回調函數
    *@encryptedData 當前得到的未解密數據
    */
    doRequestWithSession(key,url,callback,encryptedData)   
  },
})

4.後臺

後臺的服務有不少,能夠作API平臺進行統一管理:緩存

  1. 登陸接口 :domain/api/login
  2. 驗證簽名解密接口 :domain/api/decrypt
  3. 其餘

其中的解密代碼示例 (官方4種語言版本:Python、php、c++、node):

<?php 
class WXBizDataCrypt
{
    private $appid;
    private $sessionKey;

    /**
     * 構造函數
     * @param $sessionKey string 用戶在小程序登陸後獲取的會話密鑰
     * @param $appid string 小程序的appid
     */
    public function __construct( $appid, $sessionKey)
    {
        $this->sessionKey = $sessionKey;
        $this->appid = $appid;
    }


    /**
     * 檢驗數據的真實性,而且獲取解密後的明文.
     * @param $encryptedData string 加密的用戶數據
     * @param $iv string 與用戶數據一同返回的初始向量
     * @param $data string 解密後的原文
     *
     * @return int 成功0,失敗返回對應的錯誤碼
     */
    public function decryptData( $encryptedData, $iv, &$data )
    {
        if (strlen($this->sessionKey) != 24) {
            return ErrorCode::$IllegalAesKey;
        }
        $aesKey=base64_decode($this->sessionKey);

        
        if (strlen($iv) != 24) {
            return ErrorCode::$IllegalIv;
        }
        $aesIV=base64_decode($iv);

        $aesCipher=base64_decode($encryptedData);

        $result=openssl_decrypt( $aesCipher, "AES-128-CBC", $aesKey, 1, $aesIV);

        $dataObj=json_decode( $result );
        if( $dataObj  == NULL )
        {
            return ErrorCode::$IllegalBuffer;
        }
        if( $dataObj->watermark->appid != $this->appid )
        {
            return ErrorCode::$IllegalBuffer;
        }
        $data = $result;
        return ErrorCode::$OK;
    }

}

$appid = 'wx4f4bc4dec97d474b';
$sessionKey = 'tiihtNczf5v6AKRyjwEUhQ==';

$encryptedData="CiyLU1Aw2KjvrjMdj8YKliAjtP4gsMZM
                QmRzooG2xrDcvSnxIMXFufNstNGTyaGS
                9uT5geRa0W4oTOb1WT7fJlAC+oNPdbB+
                3hVbJSRgv+4lGOETKUQz6OYStslQ142d
                NCuabNPGBzlooOmB231qMM85d2/fV6Ch
                evvXvQP8Hkue1poOFtnEtpyxVLW1zAo6
                /1Xx1COxFvrc2d7UL/lmHInNlxuacJXw
                u0fjpXfz/YqYzBIBzD6WUfTIF9GRHpOn
                /Hz7saL8xz+W//FRAUid1OksQaQx4CMs
                8LOddcQhULW4ucetDf96JcR3g0gfRK4P
                C7E/r7Z6xNrXd2UIeorGj5Ef7b1pJAYB
                6Y5anaHqZ9J6nKEBvB4DnNLIVWSgARns
                /8wR2SiRS7MNACwTyrGvt9ts8p12PKFd
                lqYTopNHR1Vf7XjfhQlVsAJdNiKdYmYV
                oKlaRv85IfVunYzO0IKXsyl7JCUjCpoG
                20f0a04COwfneQAGGwd5oa+T8yO5hzuy
                Db/XcxxmK01EpqOyuxINew==";

$iv = 'r7BXXKkLb8qrSNn05n0qiA==';

$pc = new WXBizDataCrypt($appid, $sessionKey);
$errCode = $pc->decryptData($encryptedData, $iv, $data );

if ($errCode == 0) {
    print($data . "\n");
} else {
    print($errCode . "\n");
}
?>

接觸不深 ,權當記錄。錯漏處還望多多指正~

參考閱讀:

  1. 一斤代碼:微信小程序中用戶登陸和登陸態維護
  2. 官方文檔
相關文章
相關標籤/搜索