開發微信小程序時,接入小程序的授權登錄可以快速實現用戶注冊登錄的步驟,是快速建立用戶體系的重要一步。這篇文章將介紹 python + flask + 微信小程序實現用戶快速注冊登錄方案(本文主要進行后端邏輯的梳理,小程序端邏輯只寫了必要的部分,如有需要,請點擊連接閱讀官方開發文檔)
官方給出的微信小程序登錄時序圖如下:
這個流程分為兩大部分:
- 小程序使用 wx.login() API 獲取 code,並由開發者后端服務器換取open_id 和 session_key,小程序使用 wx.getUserInfo() API 獲取 encryptedData 和 iv,然后將這三個信息發送給開發者服務器服務器。
- 開發者服務器獲取到 code、encryptedData和 iv 后,將 session_key 利用 encryptedData 和 iv 解密,在服務端獲取用戶信息。根據用戶信息返回 jwt 數據,完成登錄狀態。
小程序登錄流程梳理
1 小程序端調用wx.login
小程序通過wx.login獲取微信的 code,然后將這個 code 發送給開發者服務器。
返回值
屬性 | 類型 | 說明 |
---|---|---|
code | String | 用戶允許登錄后,回調內容會帶上 code(有效期五分鍾) |
2 判斷用戶是否授權
小程序端可以通過 wx.getSetting 接口獲取用戶當前的授權狀態
- 如果用戶已經授權,則直接發起登錄請求
- 如果用戶沒有授權,則調用 wx.authorize 或
引導用戶關注公眾號
來獲得授權。 - 如果用戶不予授權,則登錄失敗。
3 小程序端訪問 wx.getUserInfo
小程序端調用 wx.getUserInfo API 來獲得用戶信息(該接口的調用需要獲得用戶授權)
接口success 時返回參數如下:
參數名 | 類型 | 說明 |
---|---|---|
userInfo | OBJECT | 用戶信息對象,不包含 openid 等敏感信息。 |
rawData | String | 不包括敏感信息的原始數據字符串,用於計算簽名。 |
signature | String | 使用 sha1( rawData + sessionkey ) 得到字符串,用於校驗用戶信息 |
encryptedData | String | 包括敏感數據在內的完整用戶信息的加密數據,詳細見加密數據解密算法 |
iv | String | 加密算法的初始向量,詳細見加密數據解密算法 |
將這些第一步獲得的js_code
和這里獲得的 rawData
, signature
, encryptedData
, iv
一起打包傳入開發者后端。
4 開發者服務器訪問code2session
開發者服務器接收到 code
之后,會進行封裝處理,通過code2Session這個api接口來獲取真正需要的微信用戶的登錄態session_key
和 openid
和 unionid
。
- 准確來說
session_key
才是真正的微信登錄態信息,但是把openid
和unionid
加起來一起理解,也可以籠統地理解為這些都是微信的登錄態信息。
請求參數
屬性 | 類型 | 默認值 | 必填 | 說明 |
---|---|---|---|---|
appid | string | 是 | 小程序 appId | |
secret | string | 是 | 小程序 appSecret | |
js_code | string | 是 | 登錄時獲取的 code | |
grant_type | string | 是 | 授權類型,此處只需填寫 authorization_code |
返回值(返回JSON數據包)
屬性 | 類型 | 說明 |
---|---|---|
openid | string | 用戶唯一標識 |
session_key | string | 會話密鑰 |
unionid | string | 用戶在開放平台的唯一標識符,在滿足 UnionID 下發條件的情況下會返回 |
errcode | number | 錯誤碼 |
errmsg | string | 錯誤信息 |
這時,服務器需要判斷返回的JSON數據包是否包含unionid:
- 如果包含,則說明用戶已經授權(關注過同一主體公眾號,或者已經登錄過小程序在后端保存unionid)
- 如果不包含,返回第1步,提醒用戶授權
5 開發者服務器校驗用戶信息
encryptedData
解密后為以下 json 結構,詳見加密數據解密算法
{
"openId": "OPENID",
"nickName": "NICKNAME",
"gender": GENDER,
"city": "CITY",
"province": "PROVINCE",
"country": "COUNTRY",
"avatarUrl": "AVATARURL",
"unionId": "UNIONID",
"watermark":
{
"appid":"APPID",
"timestamp":TIMESTAMP
}
}
解密腳本實例連接:https://res.wx.qq.com/wxdoc/dist/assets/media/aes-sample.eae1f364.zip
數據簽名校驗
為了確保開放接口返回用戶數據的安全性,微信會對明文數據進行簽名。開發者可以根據業務需要對數據包進行簽名校驗,確保數據的完整性。
- 通過調用 wx.getUserInfo 借口獲取數據時,接口會同時返回 rawData、signature,其中 signature = sha1( rawData + session_key )
數據簽名校驗具體步驟
- 如果
code
為空,返回登錄失敗。 - 如果
code
不為空,且rawData
不為空,需要進行簽名校驗:- 使用
code
,appid
、app_secret
請求code2session接口獲得session_key
和openid
- 如果接口失敗,響應
ERR_SESSION_KEY_EXCHANGE_FAILED
- 如果接口失敗,響應
- 使用簽名算法通過
rawData
和session_key
計算簽名signature2
- 對比
signature
和signature2
- 簽名不一致,響應
ERR_UNTRUSTED_RAW_DATA
- 簽名一致,解析
rawData
為wxUserInfo
- 把
openid
寫入到wxUserInfo
- 把
(code, wxUserInfo)
緩存到 Redis - 更新用戶數據庫信息(unionid為pk)
- 進入下一環節
- 把
- 簽名不一致,響應
- 使用
- 如果
code
不為空,但rawData
為空,從 Redis 根據code
查詢緩存的用戶信息- 找到用戶信息,進入下一環節
- 沒找到用戶信息(可能是過期),響應
ERR_SESSION_EXPIRED
6 生成自定義登錄狀態(使用JWTtoken)
開發者服務器需要自己生成一個自定義的登錄態(例如業務 token或者 session)來保存這些微信服務器返回來的微信登錄態相關信息(session_key
和 openid
和 unionid
),並且做關聯處理,然后返回給小程序客戶端。
- 關聯處理就是你的自定義登錄態和微信的登錄態相關聯,這樣的話就不需要維護多個登錄態,只需要維護一個就可以了。
- 關聯處理之后需要將這個自定義登錄態信息保存起來,可以放到數據庫或者本地文件或者 例如 redis 之類的緩存服務里面,以便方便后續使用,而不需要每次都請求微信服務器(微信服務器對這個請求的頻率是有限制的)。
- 自定義登錄態的信息不僅可以包含 token,也可以包含一些用戶權限信息,或者其他信息,因為是自定義的登錄態,維護也是很自定義的。
7 小程序端將服務器端生成的token儲存
小程序客戶端接收到返回的自定義登錄態信息,從而判斷用戶是否登錄成功,登錄成功的話,就將自定義登錄態信息保存到本地的存儲。到這里,登錄就完成了。
- 本地的存儲可以是微信小程序提供的
app.globaldata
,也可以是localstoage
,注意,小程序不支持cookie
。 - 保存到本地存儲的好處就是,后續使用的這個自定義登錄態就不需要再次跟服務器進行交互來獲取了,只需要調用本地存儲就行了,這里是為了優化性能和避免浪費資源。
8 小程序訪問業務接口邏輯
-
小程序客戶端訪問業務接口的時候,攜帶之前保存到本地存儲的自定義登錄態信息進行對開發者服務器(業務接口服務器)訪問。
-
開發者服務器的業務接口接收到請求,並且請求里面攜帶了自定義的登錄態,通過校驗之后,會返回相關信息。
校驗登錄是將小程序客戶端攜帶過來的自定義登錄態和開發者服務器緩存起來的自定義登錄態進行對比,會去確認是否和用戶的
openid
或者unionid
和session_key
相匹配。- 如果匹配,就可以馬上返回業務信息。
- 如果不匹配,告知小程序客戶端無法訪問業務接口,要求用戶重新登錄。
- 如果匹配結果是自定義登錄態超時了,告知小程序客戶端需要重新運行登錄邏輯。
- 如果是匹配結果是自定義登錄態沒有超時,但是微信登錄態超時了,那么要求開發者服務器就會再次發起
code2Session
進行微信登錄態更新。
由於我們的小程序使用微信號作為小程序的賬號,如果需要使用自定義的賬號,則需要再加上注冊的api和關聯賬號的邏輯。
流程總結
code
是微信用戶的登錄臨時憑證,是打開小程序登錄的時候獲取的只屬於這個微信用戶的登錄憑證,需要注意的是,這個登錄憑證只供微信小程序使用的。且同一個每一次登錄時獲得的code都不同。這個code
的存活時間一般是5分鍾左右,他的最大作用就是確定是來源自哪一個微信用戶來打開,是為了后續生成一個微信登錄態session_key
而准備的。session_key
是微信用戶在小程序里面的登錄態信息,這是微信給這個用戶頒發的一個登錄session
。這個session
一直存活直到你關閉小程序。- 以前官方是返回固定的
expire_time
的,但是后面取消了,官方的解釋是:用戶越頻繁使用小程序,session_key有效期越長,初始有效期是3天,但是這個不一定是固定的,具體看業務需求,總的原則就是維護一個自定義登錄態,自定義登錄需要和微信登錄態關聯。
- 以前官方是返回固定的
openId
,用戶在微信里面的唯一標識,但是需要跟unionid
進行一起理解。unioinId
,如果開發者擁有多個移動應用、網站應用、和公眾帳號(包括小程序),可通過unionid
來區分用戶的唯一性,因為只要是同一個微信開放平台帳號下的移動應用、網站應用和公眾帳號(包括小程序),用戶的unionid
是唯一的。換句話說,同一用戶,對同一個微信開放平台下的不同應用,unionid
是相同的。- 一般來說,
openId
就是微信用戶的唯一標識,但是因為微信產品很多,所以會出現多個微信產品使用不同的openId
來識別同一個用戶,所以建議是統一使用unioinid
作為用戶識別的依據,因為一般來說,一般的業務都會有公眾號,所以unionid
使用頻率較高。
- 一般來說,
3rd_session
是一般是指開發者服務器的登錄態,本文中並沒有使用這個概念,而是叫做自定義登錄態或者直接說JWTtoken,官方文檔或者其他一些博客中會提到它。實際上他們是相同的概念。- 當小程序登錄態過期了,自定義登錄態沒過期的時候,那么就需要在小程序打開的時候先執行一次
wx.checkSession
來檢查,如果過期了,就本地執行登錄操作,再讓開發者服務器跟微信服務器交互,獲取新的小程序登錄態,然后關聯到自定義登錄態。 - 當小程序登錄態沒過期,自定義登錄態過期了的時候,那么小程序客戶端訪問業務接口的時候,業務接口會告訴小程序客戶端,你的自定義登錄態超時了,然后小程序客戶端會重新執行登錄邏輯,然后通知開發者服務重新生成新的自定義登錄態,然后關聯之前還在使用的小程序登錄態。
- 當二者都同時過期的時候,那就肯定要發起完整的重新登錄了。
- 這樣的好處是自定義登錄態不需要重復創建,也能跟小程序登錄態一起維護管理,達到資源合理利用的效果。
- 當小程序登錄態過期了,自定義登錄態沒過期的時候,那么就需要在小程序打開的時候先執行一次
- 一般自定義登錄態的管理都會使用類似 redis 之類的東西來進行管理的,這里也涉及到一個自定義登錄態的緩存策略,緩存起來,在一定時間內不需要重新創建自定義登錄態,達到優化性能的效果。
登錄態的驗證
1 在每一次開啟小程序的時候需要檢查登錄態
如果每次打開小程序都需要用戶來登錄顯然是不合適的,如果用戶上一次的登錄態還沒有過期,則應該視用戶為已經登錄。如果過期,才需要用戶重新登錄。
有2種方式來做:
- 方式一:小程序打開的時候先檢查小程序本地是否有存儲的自定義登錄態,
- 如果沒有,則代表是首次登錄,要自動執行完整的登錄流程,
- 如果有,則需要判斷這個自定義登錄態是否過期,可以是開發者服務器提供一個接口來檢查,也可以是在這個自定義登錄態數據里面加上過期時間,判斷是否過期。
- 過期,則自動發起完整的登錄流程。
- 不過期,就繼續使用本地保存的自定義登錄態。
- 方式二:小程序打開的時候先發起
wx.checkSession
檢查微信登錄態是否過期:- 如果過期,則發起完整的登錄流程。
- 如果不過期,則繼續使用本地保存的自定義登錄態。(如果本地的自定義登錄態沒有的話,那么也是要強制發起完整的登錄流程的)
2 在每次業務請求時需要驗證登錄態
某些業務需要只有用戶登錄狀態下才可以執行,所以,我們需要封裝一個api來驗證用戶時候登錄
實際上就是檢查一下微信和自定義的登錄態是否過期