近期剛好開發到有關小程序登錄相關的邏輯,正好和大家好好聊一聊小程序登錄邏輯。小程序登錄的目的是為了更安全的賬號體系,由於和微信支付綁定的很緊密。所以這里的登錄更要要求嚴謹。之前看到過臨時tid(有效期),或保存openid等方式,感覺多存在被人抓包復制,攻擊的可能性。我通過仔細閱讀官方文檔,整理了一個,僅依靠一次性生成code的方式登錄,可以保證登錄用的“憑據”僅可使用一次,避免盜刷。
登錄是什么?
拋開網上很多官方的解釋,我認為就是讓用戶的客戶端,可以在服務端指定一個固定的數據庫Key。即用戶攜帶登錄“憑據”,服務端通過這個“憑據”轉化成后台數據庫中唯一的key(找到這個用戶的所有信息),並可以憑借這個Key完成對用戶信息的操作。而具體用什么方式,其實萬變不離其宗。即如下圖:
攜帶登錄憑證是什么?有什么要求?
每次登錄攜帶的一個密鑰(一下稱為code),這個密鑰就是打開用戶數據庫的鑰匙,是用戶身份的必要條件。那么我們對這個key有什么要求呢?
- 可用於后端校驗:說白了就是后端的用戶ID不能明文存在前端(小程序或請求參數中),我們提供的code,不是用戶ID,但是它必須可以(在服務端)轉化成用戶ID。
- 可變性:如果code長期不變,那別人就相當於掌握了code到用戶ID的映射關系,如果code長期不變,其實和直接傳用戶ID沒什么區別,也就是沒有安全性可言。順帶提一下有一些app采用的是一段有效期內不變的“有效期code”,不是說不好,對於不涉及現金支付的業務,也算是夠用。如果涉及比較嚴謹的支付邏輯,最好還是采用“一次性密鑰code”,這樣別人就很難抓包盜用你的賬戶。
- 隨機性:即使是可變code,也不能有規律可循,也就是說生成code的方法要夠隨機
搞一個“登錄憑證”吧
這樣來看,我們的第一個目標是搞到這個code,其實小程序官方提供了這樣一個API:
wx.login(Object object)
調用接口獲取登錄憑證(code)。通過憑證進而換取用戶登錄態信息,包括用戶的唯一標識(openid)及本次登錄的會話密鑰(session_key)等。用戶數據的加解密通訊需要依賴會話密鑰完成。更多使用方法詳見 小程序登錄。
注意:這個API並不是調用后就直接登錄的,通過前面的我的解釋,大家可以理解,這個其實就是那個生成“用戶登錄憑證”code,這個“登錄憑證有效期5分鍾”而且只能用一次。我們需要在http請求中攜帶這個code,以方便服務端登錄驗證並獲取用戶信息。更重要的是你需要每次發起http請求都重新生成一個(可以很好的防止“重放攻擊”),因此我建議把獲取code的代碼放在http請求的組件里,對需要登錄態的請求添加code參數。
這樣前端部分就算是准備好了,下面我們來看服務端需要做什么。
服務端怎么使用(驗證)code?
code通過參數的形式到達服務端后,我們的驗證方式也是由微信官方提供,即調用微信的“auth.code2Session”接口,接口文檔:https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html
其中,appid是注冊小程序的時候獲取的,secret也是注冊小程序的時候獲取的,不要輕易改,查看后趕緊記好,js_code就是上面我們說的“登錄憑證code”,grant_type就填“authorization_code”。然后在server端把這個請求發出去
var code = req.query.code || req.body.code || req.headers.code;
var url = `https://api.weixin.qq.com/sns/jscode2session?appid=${
wechatConfig.appId
}&secret=${
wechatConfig.secret
}&js_code=${code}&grant_type=authorization_code`;
request.get(url, function(error, response, body) {
if (!error) {
if (body && body.length > 0) {
var parseObject = JSON.parse(body);
if (!req.session) {
req.session = {};
}
// openid : parseObject.openid;
// unionId: parseObject.unionid;
// todo: About openid & unionid
next();
}
} else {
console.log('err:', error);
next();
}
服務端甚至可以用這個unionId和openid直接(或稍加改造)入庫做用戶信息的key。剩下就是各自業務邏輯的事了。
openId和unionId都是什么?
微信服務器返回兩個用戶唯一ID,這兩個id都可以標識唯一用戶,也都可以作為我們業務的賬號體系,有效期為永遠不變(遷移接口的情況不算),但是他們倆是有區別的
- openId是用戶在當前應用下的唯一id,即openid可以鎖定“用戶 + 應用”。應用的意思就是指某個公共號、某個小程序等,因此同一用戶在不同的公共號、訂閱號、小程序、小游戲都算是不同應用,它們各自的openId也不同。
- unionId是用戶在騰訊微信體系下的唯一id,即同一用戶在不同的公共號、訂閱號、小程序、小游戲下,unionId是相同的。
因此來說如果你想打通公共號(多數APP走公共號登錄)和小程序登錄的賬號體系,共享用戶數據,就要用unionId了,注意unionId是微信專屬的,QQ並不走這套邏輯,QQ走自己的登錄體系,雖然不同,但原理類似,只是微信更復雜(上面說的同一用戶多應用的情況)。
openId也是很有用的,很多支付相關的或微信官方接口都是用openId做參數的,因此我們還需要經常在兩個間轉換,有興趣的朋友可以去官網看看其他玩法(佩服微信強大的賬號體系啊)。
獲取用戶信息的授權是什么?
如果我們只想頁面上展示頭像、昵稱的話,而不打算入庫,或操作用戶的數據。完全可以在小程序上直接獲取。
小程序的button組件: https://developers.weixin.qq.com/miniprogram/dev/component/button.html
// open-type 的合法值
// getUserInfo 獲取用戶信息,可以從bindgetuserinfo回調中獲取到用戶信息
<button open-type="getUserInfo" lang="zh_CN" bindgetuserinfo="onGotUserInfo">獲取用戶信息</button>
onGotUserInfo: function(e) {
console.log(e.detail.errMsg)
console.log(e.detail.userInfo)
console.log(e.detail.rawData)
}
// 其中e.detail.userInfo中包含用戶的頭像昵稱
缺點是必須由用戶手動觸發,並有個系統提示。一旦允許,下一次有效期內就不會再彈了。
雲開發的方案也不錯
小程序的雲開發中集成了獲取登錄信息的方案,直接就能在雲函數里面獲取用戶openid等信息
附上文檔地址: https://developers.weixin.qq.com/miniprogram/dev/wxcloud/basis/getting-started.html
基本方法如下:
// 雲函數入口文件
const cloud = require('wx-server-sdk')
cloud.init()
//獲取用戶的openid
exports.main = async(event, context) => {
return event.userInfo; //返回用戶信息
}
最后附上官方的架構圖:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
以上就是我整理的小程序登錄,因為涉及支付,所以需要研究一下細節,以及如何使用,確定安全才放心,希望對大家有用。