手把手教會你小程式登入鑑權

騰訊IVWEB團隊發表於2018-04-08

導語

為了方便小程式應用使用微信登入態進行授權登入,微信小程式提供了登入授權的開放介面。乍一看文件,感覺文件上講的非常有道理,但是實現起來又真的是摸不著頭腦,不知道如何管理和維護登入態。本文就來手把手的教會大家在業務裡如何接入和維護微信登入態。

接入流程

這裡官方文件上的流程圖已經足夠清晰,我們直接就該圖展開詳述和補充。

img

首先大家看到這張圖,肯定會注意到小程式進行通訊互動的不止是小程式前端和我們自己的服務端,微信第三方服務端也參與其中,那麼微信服務端在其中扮演著怎樣的角色呢?我們一起來串一遍登入鑑權的流程就明白了。

1. 呼叫wx.login生成code

wx.login()這個API的作用就是為當前使用者生成一個臨時的登入憑證,這個臨時登入憑證的有效期只有五分鐘。我們拿到這個登入憑證後就可以進行下一步操作:獲取openidsession_key

wx.login({
    success: function(loginRes) {
        if (loginRes.code) {
            // example: 081LXytJ1Xq1Y40sg3uJ1FWntJ1LXyth
        }
    }
});

複製程式碼

2. 獲取openid和session_key

我們先來介紹下openid,用過公眾號的童鞋應該對這個標識都不陌生了,在公眾平臺裡,用來標識每個使用者在訂閱號、服務號、小程式這三種不同應用的唯一標識,也就是說每個使用者在每個應用的openid都是不一致的,所以在小程式裡,我們可以用openid來標識使用者的唯一性。

那麼session_key是用來幹嘛的呢?有了使用者標識,我們就需要讓該使用者進行登入,那麼session_key就保證了當前使用者進行會話操作的有效性,這個session_key是微信服務端給我們派發的。也就是說,我們可以用這個標識來間接地維護我們小程式使用者的登入態,那麼這個session_key是怎麼拿到的呢?我們需要在自己的服務端請求微信提供的第三方介面https://api.weixin.qq.com/sns/jscode2session,這個介面需要帶上四個引數欄位:

引數
appid 小程式的appid
secret 小程式的secret
js_code 前面呼叫wx.login派發的code
grant_type 'authorization_code'

從這幾個引數,我們可以看出,要請求這個介面必須先呼叫wx.login()來獲取到使用者當前會話的code。那麼為什麼我們要在服務端來請求這個介面呢?其實是出於安全性的考量,如果我們在前端通過request呼叫此介面,就不可避免的需要將我們小程式的appid和小程式的secret暴露在外部,同時也將微信服務端下發的session_key暴露給“有心之人”,這就給我們的業務安全帶來極大的風險。除了需要在服務端進行session_key的獲取,我們還需要注意兩點:

  • session_key和微信派發的code是一一對應的,同一code只能換取一次session_key。每次呼叫wx.login(),都會下發一個新的code和對應的session_key,為了保證使用者體驗和登入態的有效性,開發者需要清楚使用者需要重新登入時才去呼叫wx.login()

  • session_key是有時效性的,即便是不呼叫wx.login,session_key也會過期,過期時間跟使用者使用小程式的頻率成正相關,但具體的時間長短開發者和使用者都是獲取不到的

function getSessionKey (code, appid, appSecret) {
    var opt = {
        method: 'GET',
        url: 'https://api.weixin.qq.com/sns/jscode2session',
        params: {
            appid: appid,
            secret: appSecret,
            js_code: code,
            grant_type: 'authorization_code'
        }
    };
    return http(opt).then(function (response) {
        var data = response.data;
        if (!data.openid || !data.session_key || data.errcode) {
            return {
                result: -2,
                errmsg: data.errmsg || '返回資料欄位不完整'
            }
        } else {
            return data
        }
    });
}
複製程式碼

3. 生成3rd_session

前面說過通過session_key來“間接”地維護登入態,所謂間接,也就是我們需要自己維護使用者的登入態資訊,這裡也是考慮到安全性因素,如果直接使用微信服務端派發的session_key來作為業務方的登入態使用,會被“有心之人”用來獲取使用者的敏感資訊,比如wx.getUserInfo()這個介面呢,就需要session_key來配合解密微信使用者的敏感資訊。

那麼我們如果生成自己的登入態標識呢,這裡可以使用幾種常見的不可逆的雜湊演算法,比如md5、sha1等,將生成後的登入態標識(這裡我們統稱為'skey')返回給前端,並在前端維護這份登入態標識(一般是存入storage)。而在服務端呢,我們會把生成的skey存在使用者對應的資料表中,前端通過傳遞skey來存取使用者的資訊。

可以看到這裡我們使用了sha1演算法來生成了一個skey:

const crypto = require('crypto');

return getSessionKey(code, appid, secret)
    .then(resData => {
        // 選擇加密演算法生成自己的登入態標識
        const { session_key } = resData;
        const skey = encryptSha1(session_key);
    });
    
function encryptSha1(data) {
    return crypto.createHash('sha1').update(data, 'utf8').digest('hex')
}
複製程式碼

4. checkSession

前面我們將skey存入前端的storage裡,每次進行使用者資料請求時會帶上skey,那麼如果此時session_key過期呢?所以我們需要呼叫到wx.checkSession()這個API來校驗當前session_key是否已經過期,這個API並不需要傳入任何有關session_key的資訊引數,而是微信小程式自己去調自己的服務來查詢使用者最近一次生成的session_key是否過期。如果當前session_key過期,就讓使用者來重新登入,更新session_key,並將最新的skey存入使用者資料表中。

checkSession這個步驟呢,我們一般是放在小程式啟動時就校驗登入態的邏輯處,這裡貼個校驗登入態的流程圖:

img2

下面程式碼即校驗登入態的簡單流程:

let loginFlag = wx.getStorageSync('skey');
if (loginFlag) {
    // 檢查 session_key 是否過期
    wx.checkSession({
        // session_key 有效(未過期)
        success: function() {
            // 業務邏輯處理
        },
    
        // session_key 過期
        fail: function() {
            // session_key過期,重新登入
            doLogin();
        }
    });
) else {
    // 無skey,作為首次登入
    doLogin();
}
複製程式碼

5. 支援emoji表情儲存

如果需要將使用者微信名存入資料表中,那麼就確認資料表及資料列的編碼格式。因為使用者微信名可能會包含emoji圖示,而常用的UTF8編碼只支援1-3個位元組,emoji圖示剛好是4個位元組的編碼進行儲存。

這裡有兩種方式(以mysql為例):

1.設定儲存字符集

在mysql5.5.3版本後,支援將資料庫及資料表和資料列的字符集設定為utf8mb4,因此可在/etc/my.cnf設定預設字符集編碼及服務端編碼格式

// my.cnf

[client]
default-character-set=utf8mb4
[mysql]
default-character-set=utf8mb4
[mysqld]
character-set-client-handshake = FALSE
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
複製程式碼

設定完預設字符集編碼及服務端字符集編碼,如果是對已經存在的表和欄位進行編碼轉換,需要執行下面幾個步驟:

  • 設定資料庫字符集為utf8mb4
ALTER DATABASE 資料庫名稱 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;

複製程式碼
  • 設定資料表字符集為utf8mb4
ALTER TABLE 資料表名稱 CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

複製程式碼
  • 設定資料列欄位字符集為utf8mb4
ALTER TABLE 資料表名稱 CHANGE 欄位列名稱 VARCHAR(n) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

複製程式碼

這裡的COLLATE指的是排序字符集,也就是用來對儲存的字元進行排序和比較的,utf8mb4常用的collation有兩種:utf8mb4_unicode_ciutf8mb4_general_ci,一般建議使用utf8mb4_unicode_ci,因為它是基於標準的Unicode Collation Algorithm(UCA)來排序的,可以在各種語言進行精確排序。這兩種排序方式的具體區別可以參考:What's the difference between utf8_general_ci and utf8_unicode_ci

  1. 通過使用sequelize對emoji字元進行編碼入庫,使用時再進行解碼

這裡是sequelize的配置,可參考Sequelize文件

{
       dialect: 'mysql',    // 資料庫型別
       dialectOptions: {    
         charset: 'utf8mb4',
         collate: "utf8mb4_unicode_ci"
      },
}
複製程式碼

最後

前面講了微信小程式如何接入微信登入態標識的詳細流程,那麼如何獲取小程式中的使用者資料以及對使用者敏感資料進行解密,並保證使用者資料的完整性,我將在下一篇文章給大家做一個詳細地介紹。


《IVWEB 技術週刊》 震撼上線了,關注公眾號:IVWEB社群,每週定時推送優質文章。

相關文章