webview 復(fù)用微信小程序獲取用戶信息的解決方案
簡介
詳細(xì)討論如何通過迭代演進(jìn),最終實(shí)現(xiàn)一個以微信小程序作為認(rèn)證媒介的 OAuth 2 驗(yàn)證流程,在保證安全的前提下,為網(wǎng)頁提供獲取系統(tǒng)用戶信息的能力。
本文最終探討了如何將微信小程序作為系統(tǒng)的 OAuth 認(rèn)證媒介,而不是講述如何在微信小程序中對接微信登錄。
前提
整篇討論都限于通過企業(yè)認(rèn)證的微信小程序,個人版的微信小程序不適用!
企業(yè)的微信小程序和微信的對接已經(jīng)完成。
背景
企業(yè)基于微信小程序開發(fā)了完整的登錄注冊、用戶檔案等功能。隨后,企業(yè)的運(yùn)營方不斷地在營銷活動中復(fù)用微信小程序的登錄注冊和用戶信息獲取的能力,這種營銷活動頁面,通常外包給第三方開發(fā),以 webview 的形式嵌入在微信小程序中,本質(zhì)上是一個 html 網(wǎng)頁。
復(fù)用價值
節(jié)省開發(fā)成本
這種活動頁面成百上千,由不同的外包服務(wù)商獨(dú)立開發(fā),每次都要獨(dú)立開發(fā)登錄注冊的話,成本太大。從 DRY 原則上說,這些活動頁面只需要關(guān)注每次活動的具體變化的內(nèi)容即可(通常是一個不同的網(wǎng)頁小游戲)。
提升用戶體驗(yàn)
登錄注冊時,要求用戶提供手機(jī)號,這十分普遍。但是,在網(wǎng)頁中實(shí)現(xiàn)這個功能,一般都是通過用戶填寫手機(jī)號,接收驗(yàn)證碼,再填入驗(yàn)證碼由服務(wù)器驗(yàn)證。這個過程比較耗時,需要用戶的多次輸入。相反,微信小程序可以利用微信的能力,只需要用戶點(diǎn)擊一次授權(quán)按鈕,即可獲得手機(jī)號。
通過重用微信小程序的用戶登錄注冊功能,用戶感受更方便快捷。
需求分析
這些活動頁面,最終需要的用戶信息,多數(shù)情況下,只有一個,那就是用戶在系統(tǒng)中的唯一標(biāo)識。這樣,在隨后的互動中,就可以區(qū)分不同的用戶來。比如,在游戲互動中,用戶中獎了,就需要記錄是誰中了什么獎。因?yàn)檫@些互動方式都是第三方開發(fā)的,由他們來存儲這些中獎信息很自然。但最終要關(guān)聯(lián)到內(nèi)部系統(tǒng)的用戶,就需要這個用戶身份標(biāo)識。
所以,終極問題是,微信小程序的 webview 里的網(wǎng)頁,如何獲取到微信小程序的用戶唯一身份標(biāo)識?
方案零(客戶端明文傳輸用戶身份標(biāo)識,可以立即否決)
在 webview 里的互動進(jìn)行到某個時機(jī),網(wǎng)頁調(diào)起微信小程序的登錄注冊功能,用戶完成登錄注冊之后,微信小程序通過 url 回調(diào)網(wǎng)頁,在 query string 中,帶上 userId。
安全性
不安全!
開發(fā)周期
最短
修改范圍
微信小程序客戶端,回調(diào)網(wǎng)頁 url 功能
網(wǎng)頁端,接收回調(diào)并讀取 userId 參數(shù)的功能
解決了什么?
webview 中的網(wǎng)頁獲取不到 userId 的問題
不能解決什么?
沒有安全性可言。如果通過偽造 userId 直接調(diào)用網(wǎng)頁的 url,在該網(wǎng)頁的域中,用戶身份信息就被偽造了。
補(bǔ)救措施
一旦發(fā)現(xiàn)這種行為,網(wǎng)頁端能做的是改版,不展示任何用戶信息;但是數(shù)據(jù)庫里存放的錯誤映射關(guān)系,沒有辦法恢復(fù)了,不能再被使用。
流程

結(jié)論
不能使用!
方案一(jwt,不推薦)
由于前端 url 的傳遞參數(shù)容易修改,所以需要避開使用前端在不同系統(tǒng)間直接明文傳遞 userId 的方式。一個很自然的想法就是使用 jwt,將 userId 包裝在 jwt 里,由小程序傳給 webview。網(wǎng)頁端接收到 jwt 后,需要驗(yàn)證 jwt 是否有效。由于 jwt 一旦被篡改,就通不過校驗(yàn),從而可以防止任意偽造 userId 的情況。
安全性
較不安全
開發(fā)周期
很短
修改范圍
微信小程序前端,直接傳遞小程序后端的 jwt 給到 webview
網(wǎng)頁端后端,驗(yàn)證 jwt
解決了什么
方案零的安全問題。在方案零中,任何人都可以通過猜測 userId,遍歷所有網(wǎng)頁端保存過的用戶信息。采用這個方案,猜測的 userId 會通不過網(wǎng)頁端后端的驗(yàn)證。
不能解決什么
被捕獲的 jwt,在有效期內(nèi),可以被重放攻擊。用戶 Bob 仍然可以通過拿到用戶 Alice 的 jwt,打開網(wǎng)頁 url,看到 Alice 的信息。
補(bǔ)救措施
同方案零
流程

前提
這個方案由于使用了 jwt,并且要求網(wǎng)頁服務(wù)器端做驗(yàn)證,所以需要微信小程序后端和網(wǎng)頁后端共享頒發(fā) jwt 的密鑰。
結(jié)論
不推薦!
因?yàn)楹茈y把控第三方開發(fā)們,一定做了驗(yàn)證操作。而且,要共享密鑰,這不可行。
方案二(OAuth 2 客戶端憑據(jù)許可模式)
由于方案零和方案一,本質(zhì)上都是通過客戶端傳遞 userId,只是一個通過明文,一個通過 jwt。綜上所述,這都不安全,所以可以再進(jìn)一步,去掉客戶端交流用戶信息的渠道,而改為服務(wù)器端來進(jìn)行 userId 的查詢。即讓網(wǎng)頁端的服務(wù)器端來向小程序服務(wù)器端查詢 userId,但這要求網(wǎng)頁服務(wù)器端在查詢時帶上用戶的另一信息。考慮到都是在微信生態(tài),可以讓網(wǎng)頁服務(wù)器端查詢系統(tǒng)的 userId 時,帶上unionId。
安全性
較安全
開發(fā)周期
較短
修改范圍
微信小程序后端,OAuth 2 client_credential 授權(quán)功能;以及改造原有微信小程序的登錄注冊流程,在原有的登錄注冊流程之后,保存一下 unionId, userId 的映射關(guān)系,并對受信任的客戶端(對微信小程序后端是客戶端,但實(shí)現(xiàn)上通常是服務(wù)器端)開放查詢接口。
網(wǎng)頁后端,這個方案需要服務(wù)器和服務(wù)器間的溝通;另外網(wǎng)頁后端要對接微信服務(wù),實(shí)現(xiàn) unionid 的獲取功能
解決了什么
由于 url 上不用帶上明文或者加密的 userId,用戶 Bob 沒有機(jī)會偽造用戶 Alice 的身份,更不能通過猜測 userId 遍歷系統(tǒng)用戶了。
不能解決什么
一旦網(wǎng)頁端服務(wù)器獲取到了服務(wù)器端信任的令牌,就可以從微信小程序后端查詢?nèi)我庥脩舻?userId(不需要用戶授權(quán))。所以這個方案并不能防止網(wǎng)頁端服務(wù)器來遍歷會員信息。
補(bǔ)救措施
一旦發(fā)現(xiàn)網(wǎng)頁端本身存在不安全的行為,可以立即吊銷用于建立服務(wù)器端信任的 client id 和 secret,使得網(wǎng)頁端服務(wù)器不能再使用 userId 的查詢接口。
流程
網(wǎng)頁授權(quán)拿到了用戶的 unionId
網(wǎng)頁服務(wù)器端用 unionId,向小程序服務(wù)器查詢userId (server to server talk 的方式)
查到直接做后續(xù)操作
沒查到拉起小程序登錄注冊,然后小程序回調(diào)網(wǎng)頁(webview 方式)
網(wǎng)頁的邏輯回到 2,再次查詢。得到 userId,進(jìn)行后續(xù)操作

前提
網(wǎng)頁端使用和小程序在相同的開發(fā)平臺里綁定到同一公眾號主體的賬號,授權(quán)獲取 unionid。
結(jié)論
在工期緊急的情況下,推薦使用
方案三(OAuth 2 授權(quán)碼許可模式)
方案二要求系統(tǒng)信任網(wǎng)頁端,從而開放 userId 查詢權(quán)限給到網(wǎng)頁端。注意當(dāng)把用戶的 userId 給到網(wǎng)頁端時,不需要經(jīng)過用戶的同意,因?yàn)椴捎昧丝蛻舳藨{據(jù)許可模式。
本方案更進(jìn)一步,網(wǎng)頁端要獲取用戶的 userId,需要經(jīng)過用戶顯式同意,即采用 OAuth 2 的授權(quán)碼許可模式。
安全性
更安全
開發(fā)周期
最長
修改范圍
微信小程序后端采用 OAuth 2 的授權(quán)碼許可模式
微信小程序前端增加用戶授權(quán)第三方查看其信息的頁面
網(wǎng)頁端服務(wù)器對接 OAuth 2 的流程
解決了什么
這個方案解決了方案二不能防止網(wǎng)頁端遍歷系統(tǒng)用戶的問題,因?yàn)橹挥杏脩麸@式授權(quán),它才能拿到用戶信息。
不能解決什么
一旦用戶同意,userId 就“泄露”給了網(wǎng)頁端。這個網(wǎng)頁應(yīng)用自己保存的(userId, 中獎信息)這個映射關(guān)系,由這個網(wǎng)頁應(yīng)用自己負(fù)責(zé)保證安全。
補(bǔ)救措施
如果發(fā)現(xiàn)網(wǎng)頁端應(yīng)用本身存在不安全的行為,可以吊銷其 clientid/secret,使得該網(wǎng)頁端不能再訪問微信小程序后端接口。
流程
1. 網(wǎng)頁端的已有流程(方案二中的網(wǎng)頁端要求用戶授權(quán),拿用戶的 unionid,這一步可以去掉)
2. 網(wǎng)頁端拉起小程序,用戶登錄/注冊和小程序自己內(nèi)部的登錄/注冊要多一步,即展示一個頁面,明確告知用戶:該網(wǎng)頁應(yīng)用想使用你的憑據(jù),以獲取你的用戶信息。措詞可以相應(yīng)修改
3. 用戶拒絕登錄/注冊,流程結(jié)束
4. 用戶如果同意,在小程序完成了登錄注冊流程,那么小程序回調(diào)webview,在 url query string 里帶上一個后端頒發(fā)的臨時 code,有效期 5 分鐘(防止重放,有效期必須很短,而且限制只能被使用一次)
5. 網(wǎng)頁端服務(wù)器向小程序后端請求授權(quán)(client_credential)
6. 授權(quán)失敗,流程結(jié)束
7. 授權(quán)成功,使用server trust access_token 作為憑據(jù),用 code 換取用戶的 access_token,scope: userinfo
8. 換取失敗,流程結(jié)束
9. 換取成功,使用該用戶 access_token,向小程序后端查詢用戶信息(userId 等等)

結(jié)論
在時間允許的情況下,最推薦使用這個方案!
總結(jié)
本文列舉了在webview中復(fù)用微信小程序的登錄注冊功能以換取用戶信息的 4 種方案,今天就寫到這里。如果有考慮不周的地方,后面再發(fā)文修補(bǔ),再有時間甚至可以分享關(guān)鍵代碼,或者做一個開源代碼庫。
