从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机制。
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搞丢了。
下面咱就再一次使用封装后的函数来进行请求发送试验:
改造后的微信小程序端请求代码:
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的原理,你会很迷惑。
所以,还是那句话:明白原理最重要,掌握了原理也就掌握了一切。。。
晚安,同学们。