摘要:參考鏈接微信小程序七日談第五天你可能要在登錄功能上花費大力氣理解認證及實踐網站微信登錄實現最后,感謝女朋友支持。
開發微信小程序時,接入小程序的授權登錄可以快速實現用戶注冊登錄的步驟,是快速建立用戶體系的重要一步。這篇文章將介紹 python + sanic + 微信小程序實現用戶快速注冊登錄全棧方案。
微信小程序登錄時序圖如下:
這個流程分為兩大部分:
小程序使用 wx.login() API 獲取 code,調用 wx.getUserInfo() API 獲取 encryptedData 和 iv,然后將這三個信息發送給第三方服務器。
第三方服務器獲取到 code、encryptedData和 iv 后,使用 code 換取 session_key,然后將 session_key 利用 encryptedData 和 iv 解密在服務端獲取用戶信息。根據用戶信息返回 jwt 數據,完成登錄。
下面我們先看一下小程序提供的 API。
小程序登錄 API在這個授權登錄的過程中,用到的 API 如下:
wx.login
wx.getUserInfo
wx.chekSession 是可選的,這里并沒有用到。
wx.login(OBJECT)調用此接口可以獲取登錄憑證(code),以用來換取用戶登錄態信息,包括用戶的唯一標識(openid) 及本次登錄的 會話密鑰(session_key)。
如果接口調用成功,返回結果如下:
參數名 | 類型 | 說明 |
---|---|---|
errMsg | String | 調用結果 |
code | String | 用戶允許登錄后,回調內容會帶上 code(有效期五分鐘),開發者需要將 code 發送到開發者服務器后臺,使用code 換取 session_key api,將 code 換成 openid 和 session_key |
開發者服務器使用登錄憑證 code 獲取 session_key 和 openid。其中 session_key 是對用戶數據進行加密簽名的密鑰。為了自身應用安全,session_key 不應該在網絡上傳輸。所以這一步應該在服務器端實現。
wx.getUserInfo此接口用來獲取用戶信息。
當 withCredentials 為 true 時,要求此前有調用過 wx.login 且登錄態尚未過期,此時返回的數據會包含 encryptedData, iv 等敏感信息;當 withCredentials 為 false 時,不要求有登錄態,返回的數據不包含 encryptedData, iv 等敏感信息。
接口success 時返回參數如下:
參數名 | 類型 | 說明 |
---|---|---|
userInfo | OBJECT | 用戶信息對象,不包含 openid 等敏感信息 |
rawData | String | 不包括敏感信息的原始數據字符串,用于計算簽名。 |
signature | String | 使用 sha1( rawData + sessionkey ) 得到字符串,用于校驗用戶信息,參考文檔 signature。 |
encryptedData | String | 包括敏感數據在內的完整用戶信息的加密數據,詳細見加密數據解密算法 |
iv | String | 加密算法的初始向量,詳細見加密數據解密算法 |
encryptedData 解密后為以下 json 結構,詳見加密數據解密算法
{ "openId": "OPENID", "nickName": "NICKNAME", "gender": GENDER, "city": "CITY", "province": "PROVINCE", "country": "COUNTRY", "avatarUrl": "AVATARURL", "unionId": "UNIONID", "watermark": { "appid":"APPID", "timestamp":TIMESTAMP } }
服務器端提供的 API由于解密 encryptedData 需要 session_key 和 iv 所以,在給服務器端發送授權驗證的過程中需要將 code、encryptedData 和 iv 一起發送。
服務器端授權需要提供兩個 API:
/oauth/token 通過小程序提供的驗證信息獲取服務器自己的 token
/accounts/wxapp 如果登錄用戶是未注冊用戶,使用此接口注冊為新用戶。
換取第三方 token(/oauth/token)開始授權時,小程序調用此 API 嘗試換取jwt,如果用戶未注冊返回401,如果用戶發送參數錯誤,返回403。
接口 獲取 jwt 成功時返回參數如下:
參數名 | 類型 | 說明 |
---|---|---|
account_id | string | 當前授權用戶的用戶 ID |
access_token | string | jwt(登錄流程中的第三方 session_key |
token_type | string | token 類型(固定Bearer) |
小程序授權后應該先調用此接口,如果結果是用戶未注冊,則應該調用新用戶注冊的接口先注冊新用戶,注冊成功后再調用此接口換取 jwt。
新用戶注冊(/accounts/wxapp)注冊新用戶時,服務器端需要存儲當前用戶的 openid,所以和授權接口一樣,請求時需要的參數為 code、encryptedData 和 iv。
注冊成功后,將返回用戶的 ID 和注冊時間。此時,應該再次調用獲取 token 的接口去換取第三方 token,以用來下次登錄。
實現流程接口定義好之后,來看下前后端整體的授權登錄流程。
這個流程需要注意的是,在 C 步(使用 code 換取 session )之后我們得到 session_key,然后需要用 session_key 解密得到用戶數據。
然后使用 openid 判斷用戶是否已經注冊,如果用戶已經注冊,生成 jwt 返回給小程序。
如果用戶未注冊返回401, 提示用戶未注冊。
jwt(3rd_session) 用于第三方服務器和小程序之間做登錄態校驗,為了保證安全性,jwt 應該滿足:
足夠長。建議有 2^128 組合
避免使用 srand(當前時間),然后 rand() 的方法,而是采用操作系統提供的真正隨機數機制。
設置一定的有效時間,
當然,在小程序中也可以使用手機號登錄,不過這是另一個功能了,就不在這里敘述了。
代碼實現說了這么多,接下來看代碼吧。
小程序端代碼代碼邏輯為:
用戶在小程序授權
小程序將授權消息發送到服務器,服務器檢查用戶是否已經注冊,如果注冊返回 jwt,如果沒注冊提示用戶未注冊,然后小程序重新請求注冊接口,注冊用戶,注冊成功后重復這一步。
為了簡便,這里在小程序 啟動的時候就請求授權。代碼實現如下。
//app.js var config = require("./config.js") App({ onLaunch: function() { //調用API從本地緩存中獲取數據 var jwt = wx.getStorageSync("jwt"); var that = this; if (!jwt.access_token){ //檢查 jwt 是否存在 如果不存在調用登錄 that.login(); } else { console.log(jwt.account_id); } }, login: function() { // 登錄部分代碼 var that = this; wx.login({ // 調用 login 獲取 code success: function(res) { var code = res.code; wx.getUserInfo({ // 調用 getUserInfo 獲取 encryptedData 和 iv success: function(res) { // success that.globalData.userInfo = res.userInfo; var encryptedData = res.encryptedData || "encry"; var iv = res.iv || "iv"; console.log(config.basic_token); wx.request({ // 發送請求 獲取 jwt url: config.host + "/auth/oauth/token?code=" + code, header: { Authorization: config.basic_token }, data: { username: encryptedData, password: iv, grant_type: "password", auth_approach: "wxapp", }, method: "POST", success: function(res) { if (res.statusCode === 201) { // 得到 jwt 后存儲到 storage, wx.showToast({ title: "登錄成功", icon: "success" }); wx.setStorage({ key: "jwt", data: res.data }); that.globalData.access_token = res.data.access_token; that.globalData.account_id = res.data.sub; } else if (res.statusCode === 401){ // 如果沒有注冊調用注冊接口 that.register(); } else { // 提示錯誤信息 wx.showToast({ title: res.data.text, icon: "success", duration: 2000 }); } }, fail: function(res) { console.log("request token fail"); } }) }, fail: function() { // fail }, complete: function() { // complete } }) } }) }, register: function() { // 注冊代碼 var that = this; wx.login({ // 調用登錄接口獲取 code success: function(res) { var code = res.code; wx.getUserInfo({ // 調用 getUserInfo 獲取 encryptedData 和 iv success: function(res) { // success that.globalData.userInfo = res.userInfo; var encryptedData = res.encryptedData || "encry"; var iv = res.iv || "iv"; console.log(iv); wx.request({ // 請求注冊用戶接口 url: config.host + "/auth/accounts/wxapp", header: { Authorization: config.basic_token }, data: { username: encryptedData, password: iv, code: code, }, method: "POST", success: function(res) { if (res.statusCode === 201) { wx.showToast({ title: "注冊成功", icon: "success" }); that.login(); } else if (res.statusCode === 400) { wx.showToast({ title: "用戶已注冊", icon: "success" }); that.login(); } else if (res.statusCode === 403) { wx.showToast({ title: res.data.text, icon: "success" }); } console.log(res.statusCode); console.log("request token success"); }, fail: function(res) { console.log("request token fail"); } }) }, fail: function() { // fail }, complete: function() { // complete } }) } }) }, get_user_info: function(jwt) { wx.request({ url: config.host + "/auth/accounts/self", header: { Authorization: jwt.token_type + " " + jwt.access_token }, method: "GET", success: function (res) { if (res.statusCode === 201) { wx.showToast({ title: "已注冊", icon: "success" }); } else if (res.statusCode === 401 || res.statusCode === 403) { wx.showToast({ title: "未注冊", icon: "error" }); } console.log(res.statusCode); console.log("request token success"); }, fail: function (res) { console.log("request token fail"); } }) }, globalData: { userInfo: null } })服務端代碼
服務端使用 sanic 框架 + swagger_py_codegen 生成 rest-api。
數據庫使用 MongoDB,python-weixin 實現了登錄過程中 code 換取 session_key 以及 encryptedData 解密的功能,所以使用python-weixin 作為 python 微信 sdk 使用。
為了過濾無效請求,服務器端要求用戶在獲取 token 或授權時在 header 中帶上 Authorization 信息。 Authorization 在登錄前使用的是 Basic 驗證(格式 (Basic hashkey) 注 hashkey為client_id + client_secret 做BASE64處理),只是用來校驗請求的客戶端是否合法。不過Basic 基本等同于明文,并不能用它來進行嚴格的授權驗證。
jwt 原理及使用參見 理解JWT(JSON Web Token)認證及實踐
使用 swagger 生成代碼結構如下:
由于代碼太長,這里只放獲取 jwt 的邏輯:
def get_wxapp_userinfo(encrypted_data, iv, code): from weixin.lib.wxcrypt import WXBizDataCrypt from weixin import WXAPPAPI from weixin.oauth2 import OAuth2AuthExchangeError appid = Config.WXAPP_ID secret = Config.WXAPP_SECRET api = WXAPPAPI(appid=appid, app_secret=secret) try: # 使用 code 換取 session key session_info = api.exchange_code_for_session_key(code=code) except OAuth2AuthExchangeError as e: raise Unauthorized(e.code, e.description) session_key = session_info.get("session_key") crypt = WXBizDataCrypt(appid, session_key) # 解密得到 用戶信息 user_info = crypt.decrypt(encrypted_data, iv) return user_info def verify_wxapp(encrypted_data, iv, code): user_info = get_wxapp_userinfo(encrypted_data, iv, code) # 獲取 openid openid = user_info.get("openId", None) if openid: auth = Account.get_by_wxapp(openid) if not auth: raise Unauthorized("wxapp_not_registered") return auth raise Unauthorized("invalid_wxapp_code") def create_token(request): # verify basic token approach = request.json.get("auth_approach") username = request.json["username"] password = request.json["password"] if approach == "password": account = verify_password(username, password) elif approach == "wxapp": account = verify_wxapp(username, password, request.args.get("code")) if not account: return False, {} payload = { "iss": Config.ISS, "iat": int(time.time()), "exp": int(time.time()) + 86400 * 7, "aud": Config.AUDIENCE, "sub": str(account["_id"]), "nickname": account["nickname"], "scopes": ["open"] } token = jwt.encode(payload, "secret", algorithm="HS256") # 由于 account 中 _id 是一個 object 需要轉化成字符串 return True, {"access_token": token, "account_id": str(account["_id"])}
具體代碼可以在 Metis:https://github.com/gusibi/Metis 查看。
參考鏈接Note: 如果試用代碼,請先設定 oauth2_client,使用自己的配置。
不要將私密配置信息提交到 github。
《微信小程序七日談》- 第五天:你可能要在登錄功能上花費大力氣
理解JWT(JSON Web Token)認證及實踐
網站微信登錄-python 實現
最后,感謝女朋友支持。
歡迎關注(April_Louisa) | 請我喝芬達 |
---|---|
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/84479.html
摘要:參考鏈接微信小程序七日談第五天你可能要在登錄功能上花費大力氣理解認證及實踐網站微信登錄實現最后,感謝女朋友支持。 開發微信小程序時,接入小程序的授權登錄可以快速實現用戶注冊登錄的步驟,是快速建立用戶體系的重要一步。這篇文章將介紹 python + sanic + 微信小程序實現用戶快速注冊登錄全棧方案。 微信小程序登錄時序圖如下: showImg(https://segmentfaul...
這是小程序開發第二篇,主要介紹如何上傳圖片到騰訊云,之所以選擇騰訊云,是因為騰訊云免費空間大? 準備工作 上傳圖片主要是將圖片上傳到騰訊云對象存儲(COS)。 要使用對象存儲 API,需要先執行以下步驟: 購買騰訊云對象存儲(COS)服務 在騰訊云 對象存儲控制臺 里創建一個 Bucket 在控制臺 個人 API 密鑰 頁面里獲取 AppID、SecretID、SecretKey 內容 編寫一個...
這是小程序開發第二篇,主要介紹如何上傳圖片到騰訊云,之所以選擇騰訊云,是因為騰訊云免費空間大? 準備工作 上傳圖片主要是將圖片上傳到騰訊云對象存儲(COS)。 要使用對象存儲 API,需要先執行以下步驟: 購買騰訊云對象存儲(COS)服務 在騰訊云 對象存儲控制臺 里創建一個 Bucket 在控制臺 個人 API 密鑰 頁面里獲取 AppID、SecretID、SecretKey 內容 編寫一個...
??蘇州程序大白一文教你學會微信小程序開發??《??記得收藏??》 目錄 ????開講啦!!!!????蘇州程序大白?????博主介紹?前言?講講專享小程序有什么優勢? ?小程序文件分析?事件綁定?圖片問題?輪播圖swiper?自定義組件?生命周期?頁面生命周期?項目制作?緩沖事件?`es7 async`語法 ?觸底事件??下拉刷新頁面??css省略號??預覽大圖??購物車模擬??獲取地...
摘要:首先先注冊微信小程序管理一登錄微信公眾平臺二點擊立即注冊。注意這里不要用微信公眾號登錄,小程序賬號和微信公眾號是不同的。最好從項目直接入手,這里有微信小程序個例子,鏈接密碼有可能會過期,留言或者加我,給你最新的 首先先注冊微信小程序管理 一、登錄微信公眾平臺https://mp.weixin.qq.com 二、點擊立即注冊。 注意:這里不要用微信公眾號登錄,小程序賬號和微信公眾號是不...
閱讀 2473·2021-11-24 09:39
閱讀 3405·2021-11-15 11:37
閱讀 2251·2021-10-08 10:04
閱讀 3965·2021-09-09 11:54
閱讀 1882·2021-08-18 10:24
閱讀 1032·2019-08-30 11:02
閱讀 1792·2019-08-29 18:45
閱讀 1651·2019-08-29 16:33