上一篇文章 手把手教會(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ù)涉及到了用戶的 openid
及 unionid
等榜揖。那么數(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_key
和 openid
们颜。
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
方法钦铺,獲取用戶的 openid
和 session_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)注明出處等舔。