微信小程序登錄會(huì)話流程
背景
微信小程序的使用可以快速的基于場景進(jìn)行用戶圈的建立推廣,其中根據(jù)業(yè)務(wù)需要使用用戶信息以及授權(quán)過程,主要用到的有微信在開發(fā)工具中已經(jīng)封裝好的 api 函數(shù):wx.login、wx.getUserInfo、wx.checkSession、wx.getStorageSync、wx.request等

1. 關(guān)鍵詞
-
開放數(shù)據(jù)
調(diào)用 wx.getUserInfo(此接口有調(diào)整,使用該接口將不再出現(xiàn)授權(quán)彈窗,使用 <button open-type="getUserInfo" ></button>引導(dǎo)用戶主動(dòng)進(jìn)行授權(quán)操作) 成功時(shí) 返回的用戶開放數(shù)據(jù),可以在任意時(shí)候獲取,但其中的加密數(shù)據(jù)encryptedData需要使用用戶的session_key作為秘鑰進(jìn)行解密才能得到原始完整數(shù)據(jù)。為了防止返回的數(shù)據(jù)被人篡改偽造,開發(fā)者可以將 signature、rawData 發(fā)送到開發(fā)者服務(wù)器進(jìn)行校驗(yàn)。服務(wù)器利用用戶對(duì)應(yīng)的 session_key 使用相同的算法計(jì)算出簽名 signature2 ,比對(duì) signature 與 signature2 即可校驗(yàn)數(shù)據(jù)的完整性。返回結(jié)果主要包括:
參數(shù) 類型 說明
userInfo OBJECT 用戶信息對(duì)象,不包含 openid 等敏感信息
rawData String 不包括敏感信息的原始數(shù)據(jù)字符串,用于計(jì)算簽名。
signature String 使用 sha1( rawData + sessionkey ) 得到字符串,用于校驗(yàn)用戶信息,參考文檔 signature。
encryptedData String 包括敏感數(shù)據(jù)在內(nèi)的完整用戶信息的加密數(shù)據(jù),詳細(xì)見加密數(shù)據(jù)解密算法
iv String 加密算法的初始向量,詳細(xì)見加密數(shù)據(jù)解密算法
//調(diào)用成功后返回結(jié)果示例
{
encryptedData:"6exNeBvACX+EpzLNF2vYRhM0Z1tNZnBOYP0Qh6jQ4Ofv+69qY/RGVAB34aj4f364mkjAAD5pgDDJ41hGkkr/IaHJGioI3EhGSEQyrAfE1mPovV9s6Prg4rKedfBUerG4jEgXbS0GeERMtJ15tqEGz7qzcA49c1D1obZTuneImZq1vB1lXo3oUbnmK7LUAxQcgCQA==",
errMsg:"getUserInfo:ok",
iv:"X6PuFoueBpfgyjV9l9D6A==",
rawData:"{"nickName":"xxx","gender":1,"language":"zh_CN","city":"","province":"","country":"Iceland","avatarUrl":"https://wx.qlogo.cn/mmsen/vi_s/0daYVYsscPNxEvALzBTsJarXwUmlxjZJZMHPM6NNLegH4wLRcRlsA1x4bsJg/132"}",
signature:"c32wba325164258b9a332b6fdsdew2e520081348",
userInfo:
{
avatarUrl:"https://wx.qlogo.cn/mmsen/vi_s/0daYVYsscPNxEvALzBTsJarXwUmlxjZJZMHPM6NNLegH4wLRcRlsA1x4bsJg/132",
city:"",
country:"Iceland",
gender:1,
language:"zh_CN",
nickName:"xxx",
province:""
}
}
-
敏感數(shù)據(jù)獲?。用軘?shù)據(jù)encryptedData簽名signature)
對(duì)于encryptedData,開發(fā)者如需要獲取敏感數(shù)據(jù),需要對(duì)接口返回的加密數(shù)據(jù)( encryptedData )進(jìn)行對(duì)稱解密。
也就是需要在開發(fā)者服務(wù)器上利用session_key解密獲得的encryptedData,其結(jié)構(gòu)如下:
{
"openId": "OPENID",
"nickName": "NICKNAME",
"gender": GENDER,
"city": "CITY",
"province": "PROVINCE",
"country": "COUNTRY",
"avatarUrl": "AVATARURL",
"unionId": "UNIONID",
"watermark": //數(shù)據(jù)水印
{
"appid":"APPID", //敏感數(shù)據(jù)歸屬appid,開發(fā)者可校驗(yàn)此參數(shù)與自身appid是否一致
"timestamp":TIMESTAMP //敏感數(shù)據(jù)獲取的時(shí)間戳, 開發(fā)者可以用于數(shù)據(jù)時(shí)效性校驗(yàn)
}
}
-
自定義登錄態(tài)
環(huán)境:開發(fā)者服務(wù)器上
數(shù)據(jù):利用接口調(diào)用獲得的開放數(shù)據(jù)或者解密后的敏感數(shù)據(jù)以及session_key
操作:利用數(shù)據(jù)建立用戶在小程序的唯一標(biāo)識(shí),以及發(fā)送自己定義的登錄狀態(tài)信息記錄session(也可以是其他的信息記錄)給客戶端,session_key作為秘鑰,必須保存在開發(fā)者服務(wù)器上(以前接口https://api.weixin.qq.com/sns... 可以在小程序開發(fā)設(shè)置--服務(wù)器配置里設(shè)置為合法地址,現(xiàn)在不行了,只能使用第三方服務(wù)器進(jìn)行接口調(diào)用)
使用: 用戶非首次登陸——>查詢本地緩存中的自定義session——>獲取到session向開發(fā)者服務(wù)器查詢——>服務(wù)器上此session未過期可用——>用戶可正常業(yè)務(wù)請(qǐng)求(這只是登陸情況比較普遍正常的一種)
2.登錄
官方給的圖示:(大概步驟可以看官方文檔)
基本:其中使用wx.login()可以獲得開發(fā)者服務(wù)器向微信接口服務(wù)器請(qǐng)求獲得session_key等數(shù)據(jù)時(shí)所需要的參數(shù)code,開發(fā)者服務(wù)器以code+appid+app secret(小程序秘鑰,管理后臺(tái)可以獲得)換取 用戶唯一標(biāo)識(shí)openid 和 會(huì)話密鑰session_key。但每一次調(diào)用wx.login()都會(huì)更新微信接口服務(wù)器上的session_key。
-
首次登錄
獲取登錄憑證code——>發(fā)送至開發(fā)者服務(wù)器構(gòu)造接口( appid + app secret + code) 請(qǐng)求 微信接口服務(wù)器數(shù)據(jù)——>返回用戶數(shù)據(jù)密鑰session_key 和唯一標(biāo)識(shí)openid給開發(fā)者服務(wù)器——>根據(jù)session_key 和 openid等生成自定義的第三方session,存儲(chǔ)session_key至開發(fā)者服務(wù)器,不能將session_key發(fā)送至客戶端,防止敏感數(shù)據(jù)泄露——>將第三方自定義登錄信息session發(fā)送至客戶端——>保存自定義第三方登錄態(tài)信息session到本地緩存,登錄成功,可以設(shè)置一個(gè)登錄標(biāo)識(shí)flag,使flag=true表示首次登錄成功,進(jìn)行相關(guān)的處理操作。
其中,登錄校驗(yàn)憑證接口(在開發(fā)者服務(wù)器中調(diào)用,小程序中無法使用此接口)為:https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code appid 小程序唯一標(biāo)識(shí) secret 小程序的 app secret js_code 是wx.login登錄時(shí)獲取的憑證code grant_type 填寫為 authorization_code
具體偽代碼如下:
/**
* 我把這一步封裝成一個(gè)函數(shù),以后再調(diào)用那些需要登錄后才有權(quán)限訪問的后臺(tái)服務(wù)時(shí).
* 可以使用使用這個(gè)函數(shù)操作,基本邏輯,還可以進(jìn)行擴(kuò)展
* @url : 開發(fā)者服務(wù)器登錄服務(wù)接口 可設(shè)置默認(rèn)值
* @key : 存入本地緩存的第三方自定義登錄session的鍵值
* @callback : 需要用到登錄服務(wù)的回調(diào)函數(shù)(或函數(shù)集合)
*/
function Login(key,url,callback){
// 首先使用wx.login獲取登錄憑證code并發(fā)送給開發(fā)者服務(wù)器
wx.login({
success: function(res) {
if (res.code) {
//發(fā)送code給開發(fā)者服務(wù)器以獲取session_key和openid
wx.request({
url:url,
data: {
code: res.code
},
//發(fā)送至開發(fā)者服務(wù)器成功
success: function(res) {
//開發(fā)者服務(wù)器獲得session_key和openid成功并返回了
//第三方自定義session或者解密數(shù)據(jù)等json格式數(shù)據(jù)res給客戶端
if(res.has3rdSession){
//登錄成功,將第三方session存入本地緩存
console.log("登錄成功")
wx.setStorageSync(key,res.3rdSession)
//設(shè)置登錄標(biāo)識(shí)為true
this.setData{
flag:true
}
//進(jìn)行后續(xù)回調(diào)處理
callback(res)
....
}
else{
//沒有返回第三方自定義session,登錄失敗
console.log("登錄失敗")
....
}
}
})
} else {
//沒有獲取到登錄憑證code,登錄失敗
console.log('登錄失敗!' + res.errMsg)
....
}
}
})
}
-
非首次登錄
wx.getStorageSync()查詢本地緩存中是否有我們自定義的session登錄態(tài)信息,有的話證明不是第一次登錄,此時(shí)先查詢微信接口服務(wù)器上的用戶session_key是否過期,如果過期了,不管我們自己定義的session如何,都要重新登錄,設(shè)置登錄標(biāo)識(shí)flag為false;如果沒有過期,那么可以向開發(fā)者服務(wù)器發(fā)送sessio查詢用戶信息,如果開發(fā)者服務(wù)器告訴我們自己定義的session過期了,那重新登錄,設(shè)置登錄標(biāo)識(shí)flag為false,如果沒有過期,則查詢是否有這個(gè)session來獲得用戶的相關(guān)信息,具體偽代碼如下:
/**
- 我把這一步封裝成一個(gè)函數(shù),以后再調(diào)用那些需要登錄后才有權(quán)限訪問的后臺(tái)服務(wù)時(shí).
- 可以使用使用這個(gè)函數(shù)操作,基本邏輯,還可以進(jìn)行擴(kuò)展
- @url : 開發(fā)者服務(wù)器服務(wù)接口
- @key : 自己定義的session存儲(chǔ)在本地緩存中的鍵名
- @callback : 需要用到登錄服務(wù)的回調(diào)函數(shù)(或函數(shù)集合)
- @others : 其他需要用到的數(shù)據(jù)等參數(shù)
*/
function doRequestWithSession(key,url,callback,others)
{
try {
var value = wx.getStorageSync(key)
if (value) {
// 存在自定義登錄session,非首次登錄
// 查詢 用戶密鑰session_key是否過期
wx.checkSession({
success: function(){
//session_key 未過期,并且在本生命周期一直有效
//向開發(fā)者服務(wù)器發(fā)送自定義session查詢用戶信息
wx.request({
url: url,
data: value, //本地緩存的自定義Session值 可以加上其他需要的數(shù)據(jù)others等
success: function(res) {
if(res.hasSession){
//查詢成功,開發(fā)者服務(wù)器上有我們自定義的session
//根據(jù)返回結(jié)果進(jìn)行對(duì)應(yīng)的操作,先判斷session是否過期
//如果我們自定義的session已經(jīng)過期
if(res.expired){
console.log("session過期,重新登錄")
//設(shè)置登錄標(biāo)識(shí)為false
this.setData{
flag:false
}
wx.login() //重新登錄 或者 返回錯(cuò)誤碼
....
}
else{
//沒有過期,返回的結(jié)果則可以是用戶的相關(guān)信息
//進(jìn)行相關(guān)操作 登錄成功
console.log("歡迎回來")
callback(res)
....
}
}
else
{
//開發(fā)者服務(wù)器上沒有我們自定義的session,重新登錄
console.log("無此session,重新登錄")
//設(shè)置登錄標(biāo)識(shí)為false
this.setData{
flag:false
}
wx.login() //重新登錄 或者 返回錯(cuò)誤碼
....
}
}
})
},
fail: function(){
// session_key 已經(jīng)失效,需要重新執(zhí)行登錄流程
//設(shè)置登錄標(biāo)識(shí)為false
this.setData{
flag:false
}
wx.login() //重新登錄 或者 返回錯(cuò)誤碼
....
}
})
}
else{
//本地緩存中沒有自定義的session登錄態(tài)信息,則進(jìn)行首次登錄或者后續(xù)處理
//設(shè)置登錄標(biāo)識(shí)為false
this.setData{
flag:false
}
wx.login() //登錄 或者 返回錯(cuò)誤碼
....
}
} catch (e) {
// 異常處理
....
}
}
-
條件判斷登錄
需要用到登錄權(quán)限的服務(wù)時(shí),可以使用條件判斷
偽代碼:
if(this.data.flag){
//已經(jīng)登錄過,非首次登錄
doRequestWithSession(key,url,callback,others)
....
}
else{
//沒有登錄過,可以進(jìn)一步檢查本地緩存是否有3rdSession
do other things
....
}
3.獲取安全的原始數(shù)據(jù)
在第一部分已經(jīng)說過敏感數(shù)據(jù)和開放數(shù)據(jù),可以用 wx.getUserInfo獲得開放數(shù)據(jù),但是沒有登錄獲取解密密鑰session_key是無法解密敏感數(shù)據(jù)的,所以開放數(shù)據(jù)中,沒有登錄的話,我們只能獲得用戶昵稱、頭像、城市、性別、國籍等,并沒有可以標(biāo)識(shí)用戶的唯一id。所以如何拿到我們的安全的原始數(shù)據(jù),只有:獲取開放數(shù)據(jù)——>獲得登錄權(quán)限,開發(fā)者服務(wù)器獲得密鑰session_key校驗(yàn)簽名解密數(shù)據(jù)——>返回解密數(shù)據(jù),包含有openid。偽代碼:
a.wxml 文件:
//wx.getUserInfo接口有調(diào)整,使用該接口將不再出現(xiàn)授權(quán)彈窗,要使用 <button>組件引導(dǎo)用戶主動(dòng)進(jìn)行授權(quán)操作,使用此接口要注意: //當(dāng)用戶未授權(quán)過,調(diào)用該接口將直接報(bào)錯(cuò);當(dāng)用戶授權(quán)過,可以使用該接口獲取用戶信息 //例子 <button open-type='getUserInfo' bindgetuserinfo='showUserInfo'>登錄</button>
a.js 文件:
Page({
data:{},
...
//點(diǎn)擊登錄按鈕后觸發(fā)
showUserInfo:function(e){
//獲取需要解密的數(shù)據(jù)
var encryptedData = e.detail.encryptedData
/*
*@key 第三方自定義登錄session存在本地緩存的key值
*@url 解密敏感數(shù)據(jù)的開發(fā)者服務(wù)器服務(wù)接口
*@callback 獲得解密數(shù)據(jù)后的回調(diào)函數(shù)
*@encryptedData 當(dāng)前獲得的未解密數(shù)據(jù)
*/
doRequestWithSession(key,url,callback,encryptedData)
},
})
4.后臺(tái)
后臺(tái)的服務(wù)有很多,可以做API平臺(tái)進(jìn)行統(tǒng)一管理:
- 登錄接口 :domain/api/login
- 驗(yàn)證簽名解密接口 :domain/api/decrypt
- 其他
其中的解密代碼示例 (官方4種語言版本:Python、php、c++、node):
<?php
class WXBizDataCrypt
{
private $appid;
private $sessionKey;
/**
* 構(gòu)造函數(shù)
* @param $sessionKey string 用戶在小程序登錄后獲取的會(huì)話密鑰
* @param $appid string 小程序的appid
*/
public function __construct( $appid, $sessionKey)
{
$this->sessionKey = $sessionKey;
$this->appid = $appid;
}
/**
* 檢驗(yàn)數(shù)據(jù)的真實(shí)性,并且獲取解密后的明文.
* @param $encryptedData string 加密的用戶數(shù)據(jù)
* @param $iv string 與用戶數(shù)據(jù)一同返回的初始向量
* @param $data string 解密后的原文
*
* @return int 成功0,失敗返回對(duì)應(yīng)的錯(cuò)誤碼
*/
public function decryptData( $encryptedData, $iv, &$data )
{
if (strlen($this->sessionKey) != 24) {
return ErrorCode::$IllegalAesKey;
}
$aesKey=base64_decode($this->sessionKey);
if (strlen($iv) != 24) {
return ErrorCode::$IllegalIv;
}
$aesIV=base64_decode($iv);
$aesCipher=base64_decode($encryptedData);
$result=openssl_decrypt( $aesCipher, "AES-128-CBC", $aesKey, 1, $aesIV);
$dataObj=json_decode( $result );
if( $dataObj == NULL )
{
return ErrorCode::$IllegalBuffer;
}
if( $dataObj->watermark->appid != $this->appid )
{
return ErrorCode::$IllegalBuffer;
}
$data = $result;
return ErrorCode::$OK;
}
}
$appid = 'wx4f4bc4dec97d474b';
$sessionKey = 'tiihtNczf5v6AKRyjwEUhQ==';
$encryptedData="CiyLU1Aw2KjvrjMdj8YKliAjtP4gsMZM
QmRzooG2xrDcvSnxIMXFufNstNGTyaGS
9uT5geRa0W4oTOb1WT7fJlAC+oNPdbB+
3hVbJSRgv+4lGOETKUQz6OYStslQ142d
NCuabNPGBzlooOmB231qMM85d2/fV6Ch
evvXvQP8Hkue1poOFtnEtpyxVLW1zAo6
/1Xx1COxFvrc2d7UL/lmHInNlxuacJXw
u0fjpXfz/YqYzBIBzD6WUfTIF9GRHpOn
/Hz7saL8xz+W//FRAUid1OksQaQx4CMs
8LOddcQhULW4ucetDf96JcR3g0gfRK4P
C7E/r7Z6xNrXd2UIeorGj5Ef7b1pJAYB
6Y5anaHqZ9J6nKEBvB4DnNLIVWSgARns
/8wR2SiRS7MNACwTyrGvt9ts8p12PKFd
lqYTopNHR1Vf7XjfhQlVsAJdNiKdYmYV
oKlaRv85IfVunYzO0IKXsyl7JCUjCpoG
20f0a04COwfneQAGGwd5oa+T8yO5hzuy
Db/XcxxmK01EpqOyuxINew==";
$iv = 'r7BXXKkLb8qrSNn05n0qiA==';
$pc = new WXBizDataCrypt($appid, $sessionKey);
$errCode = $pc->decryptData($encryptedData, $iv, $data );
if ($errCode == 0) {
print($data . "\n");
} else {
print($errCode . "\n");
}
?>

立即掃碼關(guān)注

多門店/直營/加盟連鎖管理系統(tǒng)