從session原理出發解決微信小程式的登陸問題
對於已經熟悉了session原理的同學來說,我們都清楚:在瀏覽器端我們會儲存一個sessionId,用它來作為憑證,在伺服器端得到有關本次瀏覽器與伺服器會話的所有資訊,這些資訊是儲存在伺服器端的儲存空間中的,它完全可以用來判斷一個瀏覽器端的登入狀態,因為它是由伺服器端來掌控的,是安全的。
那麼瀏覽器端是用什麼來儲存這個sessionId? 並且瀏覽器又是如何將sessionId傳回給伺服器的呢?
大體上是有兩種方法的:
1、使用瀏覽器端實現的cookie功能,每次瀏覽器都會將伺服器傳過來的cookie內容按鍵值對的方式放到瀏覽器的快取中,然後下次請求同一個伺服器時又會將cookie內容取出來送回伺服器,當然其中就有儲存在cookie中的sessionId。
2、使用URL重寫的方法,URL地址重寫是對客戶端不支援Cookie的解決方案。URL地址重寫的原理是將該使用者Session的id資訊重寫 到URL地址中。伺服器能夠解析重寫後的URL獲取Session的id。這樣即使客戶端不支援Cookie,也可以使用Session來記錄使用者狀態。
不到迫不得已的地步,我是不會考慮第二種,也就是使用URL重寫的方法的。
其實,實際的應用場景無非就是以下兩種,我們用病人看病的例子來理一理:
第一種,醫生給病人看病,一天有幾百個病人,不可能把所有的病人的病情都記得清清楚楚,所以就需要一個病歷本,病人下次來看病的時候醫生就可以根據病人的病歷本上記錄的資訊來得到病人的病情狀況。(這種情況對應的就是瀏覽器端只使用cookie與伺服器進行互動的情況)
第二種,有一些特殊的病人,他們是毒癮患者,正在接受戒毒的輔助治療,每個月需要到醫院領取少量的類似毒品的藥物來逐步減少毒品攝入量;醫院也給他一個病歷本,但是由於使用藥物的特殊性,為了防止某些不法分子偽造病歷本去領取違禁藥物,於是就給每個病歷本上使用GUID演算法生成了一個獨一無二的病歷號,在醫生這裡也有一個本子,上面記錄了所有特殊病人的病歷號,下次病人來拿違禁藥物的時候,醫生需要對照病人的病歷號來查詢是否存在這個病人和他需要領取藥品的具體量。這樣,就可以防止非法人員冒充和病人惡意更改藥品領取量的情況發生。(這種情況對應的就是通過sessionId來查詢出本次瀏覽器與伺服器的會話資訊的情況,病歷本上的病歷號就相當於sessionId)
上面寫了這麼多關於session的原理內容就是為了下面引出正題做準備的,所以到這裡還不明白session原理的同學可以繞道去找一些關於session的文章研究研究,再回來接著往下看。
推薦一篇大神的關於session原理的文章:
https://www.cnblogs.com/linguoguo/p/5106618.html
下面展開正題,問題是這樣的:
在開發微信小程式的過程中需要實現一個小程式登陸的功能,由於小程式中與伺服器的互動大部分使用的都是HTTP通訊,所以完全可以仿照之前開發B/S的那一套登陸體系,利用上面提到的sessionId的方式在伺服器端進行登陸態的儲存,進行是否登入的判斷。相對於以前的 伺服器/瀏覽器 的開發模式,伺服器/微信小程式 的開發模式有一個初級開發者需要注意的點,就是:微信小程式是不會將HTTP報文頭中的COOKIE資訊存入快取中的,自然也就不會將COOKIE的內容傳回給伺服器端,簡單的說就是 微信小程式端沒有幫你實現cookie機制。
所以,如果你想當然的就認為微信小程式已經幫你在背後實現了cookie機制,那麼你就會像我一樣,踩入了一個大坑。
下面是我的踩坑歷程,咱從坑中領悟:
伺服器端的一段程式(.net MVC 的 Action方法程式)
public ActionResult SessionTest() { Session["TestValue"] = 1; return Json(new { message = "this is a test response" }); }
使用微信小程式的原生request方法寫的請求程式
sendRequest:function(){ wx.request({ url:'http://localhost:51112/Test/SessionTest', method:'POST', success:function(res){ console.log("進行了一次請求"); }, fail:function(){ console.log("請求失敗"); } }); }
過程就是用小程式的這段程式碼執行,去請求伺服器,伺服器執行的就是上面展示的那段action方法的程式碼。
總共請求兩次,兩次請求的http報文頭如下:
第一次請求:
(請求報文頭):
(應答報文頭):
由於這是微信小程式第一次與伺服器進行互動,所以並沒有攜帶任何cookie的內容,這是很正常的現象。等到伺服器應答瀏覽器時,就給了瀏覽器一個sessionId,欄位名為 ASP.NET_SessionId 值為 saqu0pv20q5jkd1q2dlmxcyg 。到這裡我們如果認為微信小程式已經實現了cookie機制,那麼下一次向同一個域名進行請求的時候,我們會在請求報文頭中看到會有一個cookie欄位,裡面會有上一次微信小程式從伺服器那裡得到的sessionId的值。
然而。。。
第二次請求:
(請求報文頭):
(應答報文頭):
看到這裡你就會發現它與你預想的完全不一樣了。。首先,請求報文頭中並沒有發現有上一次伺服器給微信小程式端傳的sessionId;然後,伺服器返回給小程式的報文頭中的 ASP.NET_SessionId 值變成了 cwugbbt0mmliha0ul4ccx4l2 並非原先的 saqu0pv20q5jkd1q2dlmxcyg 。
發生的一切都指向一個原因,那就是 小程式並沒有實現cookie的機制,導致小程式請求伺服器的報文頭中並沒有攜帶sessionId,伺服器拿不到sessionId,就會認為這是另外一個還沒有對伺服器請求過的客戶端,就新生成了一個sessionId給微信小程式端,所以小程式端就拿到了不同於上次的sessionId。
到現在,所有的現象以及現象背後的原因都解釋通了,那麼接下來就是怎麼解決問題。
其實解決問題的方法很簡單,既然微信小程式端沒有實現cookie機制,那麼就自己實現cookie機制唄。
思路:cookie機制最簡單的功能無非就是將伺服器返回的 應答報文中cookie部分找個地方存起來,以後再向伺服器發出請求時就將儲存的cookie內容取出,填充到請求報文頭中。
存到一個地方,存到哪呢?有兩個合適的地方:
1、微信小程式的快取。
2、微信小程式的全域性變數中。
我選擇第二種,將cookie內容存到微信小程式的全域性變數中去,下面是我自己封裝的一個自動攜帶cookie中的sessionId去請求的請求函式實現:
在封裝之前,最好先去微信小程式的官網看一下 wx.request 函式的說明。點這裡
//app.js App({ globalData: { cookie: '', //供小程式儲存cookie資料使用 } })
//帶著sessionId進行請求,自動獲取服務端返回的sessionId存入全域性變數中 function RequestBySessionId(requestParam){ //三個預設引數的值 var method = "GET"; var dataType = "json"; var responseType = "text"; //使用者輸入了引數就替換,沒輸入就使用預設的 if ("method" in requestParam) { method = requestParam.method; } if ("dataType" in requestParam) { dataType = requestParam.dataType; } if ("responseType" in requestParam) { responseType = requestParam.responseType; } var url = requestParam.url; var data = requestParam.data; var success = requestParam.success; var fail = requestParam.fail; var complete = requestParam.complete; var cookieStr = ""; //請求報文頭中cookie的字串 var Cookie = App.globalData.cookie; //獲取全域性變數中的cookie內容 cookieStr = Cookie; var header = {}; if ("header" in requestParam) { header = requestParam.header; header["Cookie"] = cookieStr; } else { header["Cookie"] = cookieStr; } wx.request({ url: url, method: method, responseType: responseType, dataType: dataType, data: data, header: header, //每次請求帶上sessionId success: function(res){ //先將檢查伺服器返回報文頭中有無sessionId,有則存到全域性變數中 var cookie = res.header["Set-Cookie"]; if (undefined != cookie) { var sessionPos; if ((sessionPos = cookie.indexOf("ASP.NET_SessionId=")) != -1) { //每次請求成功都將sessionId存入全域性變數 App.globalData.cookie = cookie.substring(sessionPos, 42); } } //執行正常的操作 success(res); }, fail: fail, complete: complete, }); }
經過了這一波封裝,就等於是有了微信小程式中請求的”神器“了,麻麻再也不用擔心我把sessionId搞丟了。
下面咱就再一次使用封裝後的函式來進行請求傳送試驗:
改造後的微信小程式端請求程式碼:
var utils = require('../../utils/util.js'); //引用 util.js 檔案
sendRequest:function(){ utils.RequestBySessionId({ url: 'http://localhost:51112/Test/SessionTest', method: 'POST', success: function (res) { console.log("進行了一次請求"); }, fail: function () { console.log("請求失敗"); } }); }
同樣的過程:用小程式的這段程式碼執行,去請求伺服器,伺服器執行的就是上面原先展示的那段action方法的程式碼。
總共請求兩次,兩次請求的http報文頭如下:
第一次請求:
(請求報文頭):
(應答報文頭):
看到這次伺服器返回的 ASP.NET_SessionId 值為 dodngz2ahcznp4r3hrmavd1c。
然後,注意了。。。
第二次請求:
(請求報文頭):
看到第二次請求報文頭中的cookie了嗎,裡面就是 ASP.NET_SessionId= dodngz2ahcznp4r3hrmavd1c,
說明了,已經成功將sessionId帶上了。。。
(應答報文頭):
伺服器返回的報文中已經沒有sessionId了,因為已經不需要了。。。
以上就是我對微信小程式中自創cookie的解決方法,其實你會發現原理其實很簡單,但是如果你沒有深入過session的原理,你會很迷惑。
所以,還是那句話:明白原理最重要,掌握了原理也就掌握了一切。。。
參考文獻:
[1]https://mp.weixin.qq.com/s?__biz=MzIyODE5NjUwNQ==&mid=2653314666&idx=1&sn=7b80a788981871dcc3a29e825875fbb4&chksm=f387579cc4f0de8abb85fd818f7f84e65ce3dca9797b9a661e761d83177b0323f6a9f8a85618&mpshare=1&scene=23&srcid=0611V5zsgxAjW1J9M7ioxnOw#rd
站在巨人的肩上