小程序中神秘的用戶數(shù)據(jù)

上一篇文章 手把手教會(huì)你小程序登錄鑒權(quán) 介紹了小程序如何進(jìn)行登錄鑒權(quán)妄壶,那么一般小程序的用戶標(biāo)識(shí)可以使用上文所述微信提供的 jscode2session 接口來換取,小程序還提供了一個(gè) getUserInfo 的API來獲取用戶數(shù)據(jù)梗醇,這個(gè)用戶數(shù)據(jù)里面也可以包含當(dāng)前的用戶標(biāo)識(shí)openid甩苛。本文就 如何獲取小程序中的用戶數(shù)據(jù)及數(shù)據(jù)完整性校驗(yàn) 等內(nèi)容來展開詳述

API介紹

wx.getUserInfo 是用來獲取用戶信息的API接口,下面是對(duì)應(yīng)的參數(shù)字段:

lang 指定返回用戶信息的語言,有三個(gè)值:lang

  • zh_CN 簡(jiǎn)體中文
  • zh_TW 繁體中文
  • en 英文滔迈,默認(rèn)為en

timeout

timeout 指定API調(diào)用的超時(shí)時(shí)間, getUserInfo API其實(shí)底層也是客戶端發(fā)起一個(gè)http請(qǐng)求,來獲取到用戶的相關(guān)數(shù)據(jù)被辑,經(jīng)過封裝后返回給小程序端燎悍,后面會(huì)給大家詳細(xì)介紹。

withCredentials

withCredentials 這個(gè)字段是一個(gè)布爾類型的值敷待,決定了在調(diào)用API時(shí)小程序返回的數(shù)據(jù)里是否帶上登錄態(tài)信息间涵,不填的話默認(rèn)該字段的值為 true

那么此時(shí)API返回的結(jié)果為:

encryptedData 為包括敏感數(shù)據(jù)在內(nèi)的完整用戶信息的加密數(shù)據(jù),敏感數(shù)據(jù)涉及到了用戶的 openidunionid 等榜揖。那么數(shù)據(jù)加密采用的算法為 AES-128-CBC 分組對(duì)稱加解密算法勾哩,后面我們對(duì)這個(gè)加密算法進(jìn)行詳細(xì)分析抗蠢。如果該字段的值為 false ,就不會(huì)返回上面這兩個(gè)字段: encryptedData , iv 思劳。

  • iv 為上述解密算法的算法初始向量迅矛。同樣我們?cè)诤竺鏁?huì)詳細(xì)介紹。
  • rawData 為一個(gè)對(duì)象字符串潜叛,里面包含了用戶的一些開放數(shù)據(jù)秽褒,分別是: nickName(微信昵稱)province(所屬省份) 威兜、 language(微信客戶端內(nèi)設(shè)置的語言類型) 销斟、 gender(用戶性別)country(所在國(guó)家) 椒舵、 city(所在城市) 蚂踊、 avatarUrl(微信頭像地址)
  • signature 為了保證數(shù)據(jù)的有效性和安全性,小程序?qū)γ魑臄?shù)據(jù)進(jìn)行了簽名笔宿。這個(gè)值是 sha1(rawData + session_key) 計(jì)算后的值犁钟, sha1 則是一種密碼的哈希函數(shù),相比于 md5 哈希函數(shù)來說抗攻擊性更強(qiáng)泼橘。
  • userInfo 字段是一個(gè)對(duì)象涝动,也是用戶開放數(shù)據(jù),和rawData展示的內(nèi)容一致炬灭,只不過rawData將對(duì)象序列化為字符串作為返回值醋粟。

API之http請(qǐng)求

前面給大家講到在客戶端內(nèi)調(diào)用 getUserInfo API時(shí),微信客戶端會(huì)向微信服務(wù)端發(fā)送一條請(qǐng)求重归,在微信開發(fā)者工具里通過 http請(qǐng)求抓包可以看到昔穴,發(fā)出了一條 https://servicewechat.com/wxa-dev-logic/jsoperatewxdata 這樣的http請(qǐng)求。

請(qǐng)求體里攜帶了幾個(gè)重要的參數(shù)提前,包括 data , grant_type 等,data字段是一個(gè)JSON字符串泳唠,里面有一個(gè)字段 api_name 狈网,其值為'webapi_userinfo'。而grant_type字段也對(duì)應(yīng)了一個(gè)值“webapi_userinfo”笨腥。

響應(yīng)體返回了一個(gè)JSON對(duì)象拓哺,首先是一個(gè) baseresponse 字段,里面包含了接口調(diào)用的返回碼 errcode 和調(diào)用結(jié)果 errmsg 脖母。該對(duì)象還返回了一個(gè) data 字段士鸥,這個(gè)data字段對(duì)應(yīng)了一個(gè)JSON字符串,里面就是通過調(diào)用API拿到的所有用戶數(shù)據(jù)信息谆级。在開發(fā)者工具內(nèi)烤礁,我們還可以看到返回了一個(gè) debug_info 字段讼积,這個(gè)里面同樣包含了用戶的數(shù)據(jù) data ,只不過這里的 data 還返回了用戶的openid脚仔,同時(shí)還返回了用戶的session_key登錄態(tài)憑據(jù)勤众。

一般我們可以在開發(fā)者工具內(nèi)通過抓包,來調(diào)試一些信息的有效性鲤脏,包括用戶的 session_keyopenid 们颜。

AES-128-CBC 加密算法

上面我們說過,在小程序里通過API獲取到的用戶完整信息 encryptedData 猎醇,是需要通過 AES-128-CBC 算法來加解密的窥突。首先我們先來了解什么是 AES-128-CBC

AES 全稱為 Advanced Encryption Standard,是美國(guó)國(guó)家標(biāo)準(zhǔn)與技術(shù)研究院(NIST)在2001年建立了電子數(shù)據(jù)的加密規(guī)范硫嘶,它是一種分組加密標(biāo)準(zhǔn)阻问,每個(gè)加密塊大小為128位,允許的密鑰長(zhǎng)度為128音半、192和256位则拷。

分組加密有五種模式,分別是

ECB(Electronic Codebook Book) 電碼本模式

CBC(Cipher Block Chaining) 密碼分組鏈接模式

CTR(Counter) 計(jì)算器模式

CFB(Cipher FeedBack) 密碼反饋模式

OFB(Output FeedBack) 輸出反饋模式

這里我們主要來看 AES-128-CBC 的分組加密算法曹鸠,即用同一組key進(jìn)行明文和密文的轉(zhuǎn)換煌茬,以128bit為一組,128bit也就是16byte彻桃,那么明文的每16字節(jié)為一組就對(duì)應(yīng)了加密后的16字節(jié)的密文坛善。如果最后剩余的明文不夠16字節(jié)時(shí),就需要進(jìn)行填充了邻眷,通常會(huì)采用 PKCS#7 (PKCS#5僅支持填充8字節(jié)的數(shù)據(jù)塊眠屎,而PKCS#7支持1-255之間的字節(jié)塊)來進(jìn)行填充。

如果最后剩余的明文為13個(gè)字節(jié)肆饶,也就是缺少了3個(gè)字節(jié)才能為一組改衩,那么這個(gè)時(shí)候就需要填充3個(gè)字節(jié)的0x03:

明文數(shù)據(jù):   05 05 05 05 05 05 05 05 05 05 05 05 05
PKCS#7填充: 05 05 05 05 05 05 05 05 05 05 05 05 05 03 03 03

若明文正好是16個(gè)字節(jié)的整數(shù)倍,最后要再加入一個(gè)16字節(jié)0x10的組再進(jìn)行加密驯镊。

因此葫督,我們發(fā)現(xiàn)PKCS#7填充的兩個(gè)特點(diǎn):

  • 填充的字節(jié)都是一個(gè)相同的字節(jié)
  • 該字節(jié)的值,就是要填充的字節(jié)的個(gè)數(shù)

我們?cè)賮硪黄鹂疵魑募用艿倪^程,CBC模式對(duì)于每個(gè)待加密的密碼塊在加密前會(huì)先與前一個(gè)密碼塊的密文進(jìn)行異或運(yùn)算板惑,然后將得到的結(jié)果再通過加密器加密橄镜,其中第一個(gè)密碼塊會(huì)與我們前文所述的 iv初始化向量 的數(shù)據(jù)塊進(jìn)行異或運(yùn)算。如下圖(圖片來自wiki百科)

但是需要明確說明的是冯乘,這里API返回的 iv 是解密算法對(duì)應(yīng)的初始化向量洽胶,而非加密算法對(duì)應(yīng)的初始化向量。所以大家肯定也就猜到了裆馒,CBC模式解密時(shí)第一個(gè)密碼塊也是需要和初始化向量進(jìn)行異或運(yùn)算的姊氓。如下圖(圖片來自wiki百科):

在小程序里丐怯,這里加密和解密的密碼器為我們上一篇文章所獲取到的經(jīng)過base64編碼的 session_key

小程序中的應(yīng)用

那么在前面我們大致了解了小程序中是如何對(duì)用戶數(shù)據(jù)進(jìn)行加密的之后他膳,我們就一起以nodejs為例來看看如何在服務(wù)端對(duì)用戶數(shù)據(jù)進(jìn)行解密响逢,以及解密后的數(shù)據(jù)完整性校驗(yàn):

在util.js文件中,定義了兩個(gè)方法:

decryptByAES 方法是利用服務(wù)端在登錄時(shí)通過微信提供的 jscode2session 接口拿到的 session_key 和調(diào)用wx.getUserInfo后將返回的 iv 初始化向量來解密 encryptedData 棕孙。

encryptedBySha1 方法是通過 sha1 哈希算法來加密 session_key 生成小程序應(yīng)用自身的用戶登錄態(tài)標(biāo)識(shí)舔亭,保證 session_key 的安全性。

// util.js
const crypto = require('crypto');
module.exports = {
    decryptByAES: function (encrypted, key, iv) {
        encrypted = new Buffer(encrypted, 'base64');
        key = new Buffer(key, 'base64');
        iv = new Buffer(iv, 'base64');
        const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv)
        let decrypted = decipher.update(encrypted, 'base64', 'utf8')
        decrypted += decipher.final('utf8');
        return decrypted
    },
    encryptBySha1: function (data) {
        return crypto.createHash('sha1').update(data, 'utf8').digest('hex')
    }
};

在auth.js文件中蟀俊,調(diào)用了上篇文章里的 getSessionKey 方法钦铺,獲取用戶的 openidsession_key ,拿到這兩者后肢预,對(duì)加密的用戶數(shù)據(jù)進(jìn)行解密操作矛洞,同時(shí)將解密后的用戶數(shù)據(jù)及用戶的session_key和skey存入數(shù)據(jù)表中。

這里需要注意到一點(diǎn):如果當(dāng)前小程序綁定了開放平臺(tái)的移動(dòng)應(yīng)用或網(wǎng)站應(yīng)用烫映,或公眾平臺(tái)的公眾號(hào)等沼本,那么 encryptedData 還會(huì)多返回一個(gè) unionId 的字段,這個(gè)unionId可在小程序和其他已綁定的平臺(tái)之間區(qū)分用戶的唯一性锭沟,也就是說 同一用戶抽兆,對(duì)同一個(gè)微信開放平臺(tái)下的不同應(yīng)用,unionid是相同的 族淮。一般辫红,我們可以用unionId來打通小程序和其他應(yīng)用之間的用戶登錄態(tài)。

// auth.js
const { decryptByAES, encryptBySha1 } = require('../util');
return getSessionKey(code, appid, secret)
    .then(resData => {
        // 選擇加密算法生成自己的登錄態(tài)標(biāo)識(shí)
        const { session_key } = resData;
        const skey = encryptBySha1(session_key);

        let decryptedData = JSON.parse(decryptByAES(encryptedData, session_key, iv));
        // 存入用戶數(shù)據(jù)表中
        return saveUserInfo({
            userInfo: decryptedData,
            session_key,
            skey
        })
    })
    .catch(err => {
        return {
            result: -10003,
            errmsg: JSON.stringify(err)
        }
    })

校驗(yàn)數(shù)據(jù)完整性和有效性

當(dāng)我們通過解密拿到用戶的完整數(shù)據(jù)后祝辣,可以對(duì)拿到的數(shù)據(jù)進(jìn)行數(shù)據(jù)的完整性和有效性校驗(yàn)贴妻,防止用戶數(shù)據(jù)被惡意篡改。這里說明如何進(jìn)行相關(guān)的數(shù)據(jù)校驗(yàn):

有效性校驗(yàn):在前面我們介紹到蝙斜,當(dāng) withCredentials 設(shè)置為true時(shí)名惩,返回的數(shù)據(jù)還會(huì)帶上一個(gè) signature 的字段,其值是 sha1(rawData + session_key) 的結(jié)果孕荠,開發(fā)者可以將所拿到的signature绢片,在自己服務(wù)端使用相同的sha1算法算出對(duì)應(yīng)的signature2,即

signature2 = encryptedBySha1(rawData + session_key);

通過對(duì)比signature與signature2是否一致岛琼,來確定用戶數(shù)據(jù)的完整性。

完整性校驗(yàn):在前面拿到的 encryptedData 并進(jìn)行相關(guān)解密操作后巢株,會(huì)看到用戶數(shù)據(jù)的object對(duì)象里存在一個(gè) watermark 的字段槐瑞,官方稱之為數(shù)據(jù)水印,這個(gè)字段結(jié)構(gòu)為:

"watermark": {
    "appid":"APPID",
    "timestamp":TIMESTAMP
}

這里開發(fā)同學(xué)可以校驗(yàn)watermark內(nèi)的appid和自身appid是否一致阁苞,以及watermark內(nèi)的數(shù)據(jù)獲取的timestamp時(shí)間戳困檩,來校驗(yàn)數(shù)據(jù)的時(shí)效性祠挫。

作者:騰訊IVWEB團(tuán)隊(duì)
鏈接:小程序中神秘的用戶數(shù)據(jù)-教程-小程序社區(qū)-微信小程序-微信小程序開發(fā)社區(qū)-小程序開發(fā)論壇-微信小程序聯(lián)盟
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)悼沿,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處等舔。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市糟趾,隨后出現(xiàn)的幾起案子慌植,更是在濱河造成了極大的恐慌,老刑警劉巖义郑,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蝶柿,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡非驮,警方通過查閱死者的電腦和手機(jī)交汤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來劫笙,“玉大人芙扎,你說我怎么就攤上這事√畲螅” “怎么了戒洼?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)栋盹。 經(jīng)常有香客問我施逾,道長(zhǎng),這世上最難降的妖魔是什么例获? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任汉额,我火速辦了婚禮,結(jié)果婚禮上榨汤,老公的妹妹穿的比我還像新娘蠕搜。我一直安慰自己,他們只是感情好收壕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布妓灌。 她就那樣靜靜地躺著,像睡著了一般蜜宪。 火紅的嫁衣襯著肌膚如雪虫埂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天圃验,我揣著相機(jī)與錄音掉伏,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛斧散,可吹牛的內(nèi)容都是我干的供常。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鸡捐,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼栈暇!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起箍镜,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤源祈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后鹿寨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體新博,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年脚草,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了赫悄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡馏慨,死狀恐怖埂淮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情写隶,我是刑警寧澤倔撞,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站慕趴,受9級(jí)特大地震影響痪蝇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜冕房,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一躏啰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧耙册,春花似錦给僵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至饶辙,卻和暖如春蹲诀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背弃揽。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工侧甫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留珊佣,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓披粟,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親冷冗。 傳聞我的和親對(duì)象是個(gè)殘疾皇子守屉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容