小程序登錄態(tài)管理的方法示例
所謂的登錄態(tài)其實就是客戶端發(fā)送請求的時候攜帶的token(通常叫做令牌),當用戶輸入賬號密碼,驗證成功之后,服務端生成一個token傳遞給客戶端,客戶端在后續(xù)的請求中攜帶這個token,服務器進行校驗,校驗成功則處理客戶端的請求,校驗失敗則要求客戶端重新去登陸。
在web項目中,我們通常使用session來管理這一過程。
客戶端首次訪問請求的時候,服務端返回一個sessionId作為cookie給客戶端,往后客戶端每次請求都帶上這個cookie與服務端進行通信,當執(zhí)行完登陸操作以后,服務端將用戶數(shù)據(jù)存入到session中;隨后的每次請求,服務端都從cookie中取出sessionId,利用sessionId去查詢session,利用session中是否含有用戶信息來判斷用戶是否有登陸。
關(guān)于cookie與session的關(guān)系,請先看筆者之前的一篇文章:淺談cookie和session
一.小程序的登錄態(tài)
要明白小程序跟傳統(tǒng)的web項目的不同之處在于它不依托于瀏覽器,所以它沒有cookie,自然無法用session來管理登錄態(tài)。這給我們的編碼造成了不小麻煩。但是其實我們可以通過在請求頭中加入鍵為JESSIONID(或者SESSION),值為sessionId的cookie來模擬這種操作。同時在服務端響應給小程序的時候,若sessionId有發(fā)生變化則再回傳給客戶端。
還有一個要注意的是,小程序也有自己的登錄態(tài),那就是session_key的生命周期,session_key是小程序中為了加密數(shù)據(jù)而提供的一個密鑰,具有一定的生命周期。查看小程序官方文檔,可以知道它是在服務端調(diào)用code2Session獲取的??梢酝ㄟ^小程序的wx.checkSession()來校驗小程序端的登錄態(tài)是否過期。
弄清楚了上述兩點,我們的要解決的問題包括。
1.校驗小程序的登錄態(tài)
2.校驗服務端的登錄態(tài),即是否能從session中拿到用戶數(shù)據(jù)。
3.任何一方的登錄態(tài)過期,都調(diào)用登陸的相關(guān)代碼,注意登陸的相關(guān)代碼包含小程序端和服務端。后續(xù)會說。
4.用戶信息如何儲存。在web項目里,我們是將用戶信息存放在session里,這樣在服務端就可以直接用,而借助jsp的某些標簽,在jsp頁面我們也可以直接從session中拿出用戶數(shù)據(jù)。但現(xiàn)在是小程序,在服務端我們依然可以從session中獲取用戶數(shù)據(jù),但是在客戶端,必須等待服務端的回傳。這樣每次請求都響應用戶數(shù)據(jù)的做法顯然不是很合理的,所以我們可以將用戶數(shù)據(jù)保存在微信的緩存里。
5.攔截器問題,在web項目中,我們會在服務端給每個controller寫攔截器,攔截器一般是判斷登錄態(tài),判斷成功則執(zhí)行controller中的代碼,失敗的話,我們一般會重定向到登陸頁面,或者執(zhí)行完登陸代碼后重定向到某個特定頁面(微信站中這樣做的)。但是這種做法在小程序中是無效的,小程序是動靜分離的,我們不可能從服務端去重定向到小程序的特定頁面,也不可能從服務端去調(diào)用小程序的wx.login()方法。所以,我們把這種攔截校驗的發(fā)起從服務端移到小程序端。讓小程序主動發(fā)起這種校驗,也就是第二點的檢查服務端登錄態(tài)。
二.小程序登錄態(tài)的方案
經(jīng)過上面的分析,我們整理出小程序登錄態(tài)的方案。
1.在需要用戶登錄態(tài)的頁面,首先從緩存中獲取用戶數(shù)據(jù)userInfo,若無數(shù)據(jù),則跳4
2.調(diào)用wx.checkSession()檢查小程序端的登錄態(tài)是否過期,若沒過期,跳3,若過期,跳4
3.調(diào)用服務端的代碼檢查session是否過期(即檢查服務端的登錄態(tài)),若沒過期則拿到用戶數(shù)據(jù)繼續(xù)執(zhí)行后續(xù)的操作。若過期,則跳4.
4.登錄操作,登錄操作分為如下幾個步驟。
--a.小程序端調(diào)用wx.login()接口得到code。(code只能使用一次)
--b.服務端利用這個code訪問code2Session接口得到session_key和open_id,并將session_key和open_id存入到session中。
--c.服務端執(zhí)行登錄操作,主要是通過open_id去數(shù)據(jù)庫中尋找用戶數(shù)據(jù),若無則新增用戶到數(shù)據(jù)庫,若有則取出用戶數(shù)據(jù)。
--d.將用戶數(shù)據(jù)userInfo,session_key,open_id等數(shù)據(jù)都存放到session中,方便服務端下次拿。
--e.將用戶數(shù)據(jù)userInfo,連同session的sessionId一起響應給小程序端。
--f.小程序端得到用戶數(shù)據(jù)和userInfo后更新緩存中的userInfo(包括JESSIONID的值sessionId)
上述過程可以用微信官方的這張圖來表示。

這邊的自定義登錄態(tài)就是sessionId,自定義登錄態(tài)與session_key,openid關(guān)聯(lián)就是將session_key,openid存入到session中。
下面我們來看具體的代碼吧。
1.因為很多頁面需要取到用戶的數(shù)據(jù)才能繼續(xù)操作,所以我們在app.js里面寫一個getUseInfo方法,供各子頁面調(diào)用,方法如下。
//獲取用戶信息,傳遞的是一個回調(diào)函數(shù),獲取到用戶信息后執(zhí)行回調(diào)函數(shù),傳入的參數(shù)是userInfo
getUserInfo: function (cb) {
const _this = this ;
wx.checkSession({
success: function () {
let userInfo = wx.getStorageSync( 'userInfo' ); //先從內(nèi)存中獲取userInfo
if (userInfo.result == 1 ) {
_this.refreshSession(cb);
} else {
_this.userLogin(cb);
}
},
fail: function () {
_this.userLogin(cb);
}
})
},
上述方法的參數(shù)是一個回調(diào)函數(shù),不同的頁面在獲取了userInfo以后傳入不同的回調(diào)函數(shù),回調(diào)函數(shù)的參數(shù)就是要獲取的userInfo。
首先,調(diào)用wx.checkSession()方法判定小程序端登錄態(tài)是否失效,失效的話則去執(zhí)行userLogin(cb)操作,未失效則從緩存中去拿userInfo數(shù)據(jù)。在userInfo中,我們主要存放的是userName,userFace等用戶數(shù)據(jù)和SESSION,還有一個標志位result,用于判斷userInfo緩存數(shù)據(jù)是否失效。
然后,如果我們能從緩存中拿到用戶數(shù)據(jù),就要 檢驗服務端的登錄態(tài)是否通過。訪問refreshSession(cb)方法。代碼如下
//檢查服務端session是否過期
refreshSession: function (cb) {
const _this = this ;
let userInfo = wx.getStorageSync( 'userInfo' );
wx.request({
url: _this.domain + _this.api.xcxCheckSessionReq,
method: 'GET' ,
header: {
'Cookie' : 'JSESSIONID=' + userInfo.SESSION + ';SESSION=' + userInfo.SESSION,
},
success: function (res) {
if (res.data == 1) {
_this.globalData.userInfo = userInfo;
typeof cb == "function" && cb(_this.globalData.userInfo);
} else {
wx.removeStorageSync( 'userInfo' );
_this.userLogin(cb);
}
},
fail: function () {
wx.removeStorageSync( 'userInfo' );
_this.userLogin(cb);
}
})
},
此處,調(diào)用服務端的接口來驗證服務端的session是否已經(jīng)過期,服務端的代碼如下:
public String xcxCheckSession() {
Integer result;
HttpServletRequest req = ServletActionContext.getRequest();
HttpSession s = req.getSession();
if (s.getAttribute( "c_userId" )!= null ){
result=1;
} else {
result=0;
}
OutPutMsg.outPutMsg(result.toString());
return null ;
}
其中OutPutMsg方法就是將結(jié)果響應給客戶端。
上述代碼根據(jù)小程序端傳過來的JSESSIONID或者SESSION的值,利用servlet的特性,根據(jù)這個值去獲取session,再判斷session中是否有用戶信息。從而完成服務端的登錄態(tài)校驗。其實原理跟我們在服務端使用攔截器校驗session是否過期是一樣的。
若服務端登錄態(tài)校驗失敗,則需要清空緩存中的userInfo信息,然后去執(zhí)行userLogin(cb)方法,進行登錄。
2.登錄操作涉及到小程序端和服務端,小程序端的代碼如下:
userLogin: function (cb) {
const _this = this ;
wx.login({
success: function (res) {
//獲取code然后去訪問服務端登錄接口,code主要是為了換openId和session_key。
if (res.code) {
wx.request({
url: _this.domain + _this.api.loginCheckReq,
method: 'POST' ,
header: {
'Content-Type' : _this.globalData.postHeader
},
data: {
jsCode: res.code,
},
success: function (res) {
//登錄成功
if (res.data.result == 1) {
wx.getUserInfo({
withCredentials: true ,
success: function (result) {
res.data.wechatUserInfo = result.userInfo;
_this.globalData.userInfo = res.data;
_this.globalData.userInfo.face = '/uploadFiles/' + res.data.userFace;
typeof cb == "function" && cb(_this.globalData.userInfo)
wx.setStorageSync( 'userInfo' , _this.globalData.userInfo); //將用戶數(shù)據(jù)存入內(nèi)存
},
fail: function () {
_this.globalData.userInfo = res.data;
_this.globalData.userInfo.face = res.data.prefix + '/uploadFiles/' + res.data.userFace;
typeof cb == "function" && cb(_this.globalData.userInfo)
wx.setStorageSync( 'userInfo' , _this.globalData.userInfo);
}
})
}
}
})
}
}
})
},
首先小程序端訪問wx.login()接口獲取code,然后調(diào)用服務端的登錄代碼。服務端的登錄偽代碼如下:
public String xcxLogin(){
Integer result;
Map<String,Object>map= new HashMap<String, Object>();
try {
HttpServletRequest req = ServletActionContext.getRequest();
String jsCode = req.getParameter( "jsCode" );
String url = "https://api.weixin.qq.com/sns/jscode2session?appid="
+ ConfigUtil.XCX_APP_ID + "&secret="
+ ConfigUtil.XCX_APP_SECRET + "&js_code=" + jsCode
+ "&grant_type=authorization_code" ;
String urlDetail = URLConnectionUtil.getUrlDetail(url); //訪問小程序接口,獲取openId,session_key
JSONObject jsonObject = JSONObject.fromObject(urlDetail);
String openId=jsonObject.getString( "openid" );
String session_key=jsonObject.getString( "session_key" );
TUser user=getUserByOpenId(openId);
if (user== null ){
//新增用戶,插入到數(shù)據(jù)庫
TUser userTmp= new TUser();
user.setOpenId(openId);
addUser(userTmp);
user=userTmp;
}
session.put( "user" , user); //將user信息放入session
session.put( "session_key" , session_key); //將session_key放入session
map.put( "user" , user); //將user信息響應給小程序端
map.put( "SESSION" , req.getSession().getId()); //將sessionId響應給小程序端
result= 1 ; //登錄操作成功的標志位
} catch (Exception e) {
e.printStackTrace();
}
map.put( "result" , result);
JSONObject resInfo=JsonUtil.mapToJsonObject(map);
OutPutMsg.outPutMsg(resInfo.toString()); //將數(shù)據(jù)響應給小程序端
return null ;
}
先根據(jù)code去拿到openId和session_key,然后從數(shù)據(jù)庫去查詢是否有這個openId的客戶,沒有的話直接執(zhí)行新增操作,然后將user信息(包含openId)和session_key信息存入session,方便服務端下次直接獲取。再把user信息和sessionId回傳給小程序端。
小程序端拿到這些信息,就可以把他們緩存起來,以備下次使用啦。
3.最后,凡事需要用戶登錄才能進入的頁面,我們都讓他調(diào)用getUserInfo(cb),并傳入cb回調(diào)方法,比如。
onShow: function () {
const _this = this ;
app.getUserInfo( function (userInfo) {
_this.setData({
userInfo: userInfo,
})
});
},
三.其他注意點
關(guān)于上述代碼的userLogin()部分,目前主流的有兩種。
1.使用wx.login()靜默授權(quán),獲取用戶的openId(),不要求用戶綁定手機號,只在涉及到需要用戶手機號的時候才讓用戶來綁定手機號。只需要在userInfo中預留一個標記用戶是否有綁定手機號的字段即可。本文介紹的是采用這種登錄方式。
2.必須要用戶登錄輸入手機號及驗證碼才算登錄成功,則將userLogin處的邏輯改為跳轉(zhuǎn)至登錄頁面。然后服務端的判斷邏輯則改為通過手機號和驗證碼來確認用戶是否登錄成功。其他部分的邏輯不變,這也是目前比較主流的做法
3:可以簡單的理解wx.login()接口是靜默授權(quán),它能得到用戶的openId;而wx.getUserInfo()需要用戶授權(quán),可以獲取到用戶的頭像,昵稱等信息。還可以通過wx.getUserInfo()獲取到unionId等私密信息,但是必須得在已經(jīng)調(diào)用過wx.login()且登錄態(tài)尚未過期的前提下。
四.unionId機制
如果開發(fā)者擁有多個移動應用、網(wǎng)站應用、和公眾帳號(包括小程序),可通過 UnionID 來區(qū)分用戶的唯一性,因為只要是同一個微信開放平臺帳號下的移動應用、網(wǎng)站應用和公眾帳號(包括小程序),用戶的 UnionID 是唯一的。換句話說,同一用戶,對同一個微信開放平臺下的不同應用,unionid是相同的。
綁定了開發(fā)者帳號的小程序,可以通過下面 4 種途徑獲取 UnionID。
1.調(diào)用接口 wx.getUserInfo,從解密數(shù)據(jù)中獲取 UnionID。注意本接口需要用戶授權(quán),請開發(fā)者妥善處理用戶拒絕授權(quán)后的情況。
2.如果開發(fā)者帳號下存在同主體的公眾號,并且該用戶已經(jīng)關(guān)注了該公眾號。開發(fā)者可以直接通過 wx.login + code2Session 獲取到該用戶 UnionID,無須用戶再次授權(quán)。
3.如果開發(fā)者帳號下存在同主體的公眾號或移動應用,并且該用戶已經(jīng)授權(quán)登錄過該公眾號或移動應用。開發(fā)者也可以直接通過 wx.login + code2Session 獲取到該用戶 UnionID ,無須用戶再次授權(quán)。
4.小程序端調(diào)用云函數(shù)時,當滿足 UnionID 獲取條件時可在云函數(shù)中通過 cloud.getWXContext 獲取 UnionID
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
JavaScript將數(shù)組轉(zhuǎn)換為鏈表的方法
這篇文章主要介紹了JavaScript將數(shù)組轉(zhuǎn)換為鏈表的方法,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2020-02-02
layer插件實現(xiàn)在彈出層中彈出一警告提示并關(guān)閉彈出層的方法
今天小編就為大家分享一篇layer插件實現(xiàn)在彈出層中彈出一警告提示并關(guān)閉彈出層的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-09-09
JS數(shù)組方法shift()、unshift()用法實例分析
這篇文章主要介紹了JS數(shù)組方法shift()、unshift()用法,結(jié)合實例形式分析了JavaScript數(shù)組shift()與unshift()方法功能、用法及相關(guān)操作注意事項,需要的朋友可以參考下2020-01-01
JS解析json數(shù)據(jù)并將json字符串轉(zhuǎn)化為數(shù)組的實現(xiàn)方法
json數(shù)據(jù)在ajax實現(xiàn)異步交互時起到了很重要的作用,他可以返回請求的數(shù)據(jù),然后利用客戶端的js進行解析,這一點體現(xiàn)出js的強大,本文介紹JS解析json數(shù)據(jù)并將json字符串轉(zhuǎn)化為數(shù)組的實現(xiàn)方法,需要了解的朋友可以參考下2012-12-12

