轉載註明出處www.xdxxdxxdx.com,或者加入java學習羣481845043。java
所謂的登陸態其實就是客戶端發送請求的時候攜帶的token(一般叫作令牌),當用戶輸入帳號密碼,驗證成功以後,服務端生成一個token傳遞給客戶端,客戶端在後續的請求中攜帶這個token,服務器進行校驗,校驗成功則處理客戶端的請求,校驗失敗則要求客戶端從新去登錄。web
在web項目中,咱們一般使用session來管理這一過程。數據庫
客戶端首次訪問請求的時候,服務端返回一個sessionId做爲cookie給客戶端,日後客戶端每次請求都帶上這個cookie與服務端進行通訊,當執行完登錄操做之後,服務端將用戶數據存入到session中;隨後的每次請求,服務端都從cookie中取出sessionId,利用sessionId去查詢session,利用session中是否含有用戶信息來判斷用戶是否有登錄。json
關於cookie與session的關係,請先看筆者以前的一篇文章:淺談cookie和session小程序
要明白小程序跟傳統的web項目的不一樣之處在於它不依託於瀏覽器,因此它沒有cookie,天然沒法用session來管理登陸態。這給咱們的編碼形成了不小麻煩。可是其實咱們能夠經過在請求頭中加入鍵爲JESSIONID(或者SESSION),值爲sessionId的cookie來模擬這種操做。同時在服務端響應給小程序的時候,若sessionId有發生變化則再回傳給客戶端。api
還有一個要注意的是,小程序也有本身的登陸態,那就是session_key的生命週期,session_key是小程序中爲了加密數據而提供的一個密鑰,具備必定的生命週期。查看小程序官方文檔,能夠知道它是在服務端調用code2Session獲取的。能夠經過小程序的wx.checkSession()來校驗小程序端的登陸態是否過時。瀏覽器
弄清楚了上述兩點,咱們的要解決的問題包括。緩存
1.校驗小程序的登陸態服務器
2.校驗服務端的登陸態,便是否能從session中拿到用戶數據。微信
3.任何一方的登陸態過時,都調用登錄的相關代碼,注意登錄的相關代碼包含小程序端和服務端。後續會說。
4.用戶信息如何儲存。在web項目裏,咱們是將用戶信息存放在session裏,這樣在服務端就能夠直接用,而藉助jsp的某些標籤,在jsp頁面咱們也能夠直接從session中拿出用戶數據。但如今是小程序,在服務端咱們依然能夠從session中獲取用戶數據,可是在客戶端,必須等待服務端的回傳。這樣每次請求都響應用戶數據的作法顯然不是很合理的,因此咱們能夠將用戶數據保存在微信的緩存裏。
5.攔截器問題,在web項目中,咱們會在服務端給每一個controller寫攔截器,攔截器通常是判斷登陸態,判斷成功則執行controller中的代碼,失敗的話,咱們通常會重定向到登錄頁面,或者執行完登錄代碼後重定向到某個特定頁面(微信站中這樣作的)。可是這種作法在小程序中是無效的,小程序是動靜分離的,咱們不可能從服務端去重定向到小程序的特定頁面,也不可能從服務端去調用小程序的wx.login()方法。因此,咱們把這種攔截校驗的發起從服務端移到小程序端。讓小程序主動發起這種校驗,也就是第二點的檢查服務端登陸態。
通過上面的分析,咱們整理出小程序登陸態的方案。
1.在須要用戶登陸態的頁面,首先從緩存中獲取用戶數據userInfo,若無數據,則跳4
2.調用wx.checkSession()檢查小程序端的登陸態是否過時,若沒過時,跳3,若過時,跳4
3.調用服務端的代碼檢查session是否過時(即檢查服務端的登陸態),若沒過時則拿到用戶數據繼續執行後續的操做。若過時,則跳4.
4.登陸操做,登陸操做分爲以下幾個步驟。
--a.小程序端調用wx.login()接口獲得code。(code只能使用一次)
--b.服務端利用這個code訪問code2Session接口獲得session_key和open_id,並將session_key和open_id存入到session中。
--c.服務端執行登陸操做,主要是經過open_id去數據庫中尋找用戶數據,若無則新增用戶到數據庫,如有則取出用戶數據。
--d.將用戶數據userInfo,session_key,open_id等數據都存放到session中,方便服務端下次拿。
--e.將用戶數據userInfo,連同session的sessionId一塊兒響應給小程序端。
--f.小程序端獲得用戶數據和userInfo後更新緩存中的userInfo(包括JESSIONID的值sessionId)
上述過程能夠用微信官方的這張圖來表示。
這邊的自定義登陸態就是sessionId,自定義登陸態與session_key,openid關聯就是將session_key,openid存入到session中。
下面咱們來看具體的代碼吧。
1.由於不少頁面須要取到用戶的數據才能繼續操做,因此咱們在app.js裏面寫一個getUseInfo方法,供各子頁面調用,方法以下。
//獲取用戶信息,傳遞的是一個回調函數,獲取到用戶信息後執行回調函數,傳入的參數是userInfo
getUserInfo: function (cb) {
const
_this =
this
;
wx.checkSession({
success: function () {
let userInfo = wx.getStorageSync(
'userInfo'
);
//先從內存中獲取userInfo
if
(userInfo.result ==
1
) {
_this.refreshSession(cb);
}
else
{
_this.userLogin(cb);
}
},
fail: function () {
_this.userLogin(cb);
}
})
},
|
上述方法的參數是一個回調函數,不一樣的頁面在獲取了userInfo之後傳入不一樣的回調函數,回調函數的參數就是要獲取的userInfo。
首先,調用wx.checkSession()方法斷定小程序端登陸態是否失效,失效的話則去執行userLogin(cb)操做,未失效則從緩存中去拿userInfo數據。在userInfo中,咱們主要存放的是userName,userFace等用戶數據和SESSION,還有一個標誌位result,用於判斷userInfo緩存數據是否失效。
而後,若是咱們能從緩存中拿到用戶數據,就要 檢驗服務端的登陸態是否經過。訪問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);
}
})
},
|
此處,調用服務端的接口來驗證服務端的session是否已通過期,服務端的代碼以下:
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方法就是將結果響應給客戶端。
上述代碼根據小程序端傳過來的JSESSIONID或者SESSION的值,利用servlet的特性,根據這個值去獲取session,再判斷session中是否有用戶信息。從而完成服務端的登陸態校驗。其實原理跟咱們在服務端使用攔截器校驗session是否過時是同樣的。
若服務端登陸態校驗失敗,則須要清空緩存中的userInfo信息,而後去執行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);
//將用戶數據存入內存
},
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,而後調用服務端的登陸代碼。服務端的登陸僞代碼以下:
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
){
//新增用戶,插入到數據庫
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());
//將數據響應給小程序端
return
null
;
}
|
先根據code去拿到openId和session_key,而後從數據庫去查詢是否有這個openId的客戶,沒有的話直接執行新增操做,而後將user信息(包含openId)和session_key信息存入session,方便服務端下次直接獲取。再把user信息和sessionId回傳給小程序端。
小程序端拿到這些信息,就能夠把他們緩存起來,以備下次使用啦。
3.最後,凡事須要用戶登陸才能進入的頁面,咱們都讓他調用getUserInfo(cb),並傳入cb回調方法,好比。
onShow:
function
() {
const _this =
this
;
app.getUserInfo(
function
(userInfo) {
_this.setData({
userInfo: userInfo,
})
});
},
|
關於上述代碼的userLogin()部分,目前主流的有兩種。
1.使用wx.login()靜默受權,獲取用戶的openId(),不要求用戶綁定手機號,只在涉及到須要用戶手機號的時候才讓用戶來綁定手機號。只須要在userInfo中預留一個標記用戶是否有綁定手機號的字段便可。本文介紹的是採用這種登陸方式。
2.必需要用戶登陸輸入手機號及驗證碼纔算登陸成功,則將userLogin處的邏輯改成跳轉至登陸頁面。而後服務端的判斷邏輯則改成經過手機號和驗證碼來確認用戶是否登陸成功。其餘部分的邏輯不變,這也是目前比較主流的作法
3:能夠簡單的理解wx.login()接口是靜默受權,它能獲得用戶的openId;而wx.getUserInfo()須要用戶受權,能夠獲取到用戶的頭像,暱稱等信息。還能夠經過wx.getUserInfo()獲取到unionId等私密信息,可是必須得在已經調用過wx.login()且登陸態還沒有過時的前提下。
若是開發者擁有多個移動應用、網站應用、和公衆賬號(包括小程序),可經過 UnionID 來區分用戶的惟一性,由於只要是同一個微信開放平臺賬號下的移動應用、網站應用和公衆賬號(包括小程序),用戶的 UnionID 是惟一的。換句話說,同一用戶,對同一個微信開放平臺下的不一樣應用,unionid是相同的。
綁定了開發者賬號的小程序,能夠經過下面 4 種途徑獲取 UnionID。
1.調用接口 wx.getUserInfo,從解密數據中獲取 UnionID。注意本接口須要用戶受權,請開發者妥善處理用戶拒絕受權後的狀況。
2.若是開發者賬號下存在同主體的公衆號,而且該用戶已經關注了該公衆號。開發者能夠直接經過 wx.login + code2Session 獲取到該用戶 UnionID,無須用戶再次受權。
3.若是開發者賬號下存在同主體的公衆號或移動應用,而且該用戶已經受權登陸過該公衆號或移動應用。開發者也能夠直接經過 wx.login + code2Session 獲取到該用戶 UnionID ,無須用戶再次受權。
4.小程序端調用雲函數時,當知足 UnionID 獲取條件時可在雲函數中經過 cloud.getWXContext 獲取 UnionID