小程序用户登录信息存储及自定义登录态详解

前言.

  1. 为了更好的在服务器存储用户信息,保证用户在小程序与服务器传输数据的安全性。又因为小程序是没有Cookie的,在此处自定义登录态(伪Cookie)来识别用户。
  2. 有些开发者使用用户登录某个小程序的openid传到小程序,或者直接在小程序的某个js文件中从微信官方接口获取openid,这些对数据都是不安全的,别人可以爬取你的j s文件,或者能在你将数据从服务器传到小程序的时侯获取你的数据。
  3. 所以,我们最好不要在微信小程序和后台间传输重要数据。

接下来我们按照下面的登录时序图来解决小程序的用户登录及自定义session(后台部分我使用的PHP的yii)
小程序用户登录信息存储及自定义登录态详解-编程之家

第一步 小程序调用登录接口

通过小程序调用后台登录接口,小程序POST数据:wx.login()的codewx.getUserInfo()的encryptedDataiv
此处:
encryptedData是包含敏感信息的用户信息, 是加密的;
iv是加密算法的初始向量;
总的来说:登录用户信=session_key+encryptedData+iv,可以解出官方加密的用户信息。
这样我们在后端保存的用户信息就可以保证是安全的。有些人可能会问,为什么不直接在小程序将用户信息传到服务器存储呢,因为有可能信息会被劫持并传回伪造的信息。所以不选择直接从客户端传用户信息到服务器。
以下是小程序调用接口代码+后端操作代码

bindGetUserInfo: function(e) {if (e.detail.userInfo) {//用户按了允许授权按钮var that = this;var code = '', _3rd_session = '';wx.login({//获取codesuccess: function(res) {code = res.code //返回codewx.getUserInfo({success: function (res) {app.globalData.userInfo = res.userInfo;wx.request({url: app.globalData.url + 'WxLogin',header: {},method: 'Post',data: {code: code,encryptedData: res.encryptedData,iv: res.iv,},success: function (res) {_3rd_session = res.data._3rd_session //返回登录态(用户标识)wx.setStorageSync('_3rd_session', _3rd_session);//获取sessionIdwx.setStorageSync("sessionid", res.header["Set-Cookie"]);//console.log(res.data.timestamp)},fail: function (err) {console.log('error', err);}})}})  }})//授权成功后,跳转进入小程序首页wx.switchTab({url: '/pages/index/index'})} else {//用户按了拒绝按钮wx.showModal({title: '警告',content: '您点击了拒绝授权,将无法进入小程序,请授权之后再进入!!!',showCancel: false,confirmText: '返回授权',success: function(res) {if (res.confirm) {console.log('用户点击了“返回授权”')}}})}},

接口代码:

    //微信登录public function actionWxLogin() //获取openid,自定义登录态{$data=array();$post=file_get_contents("php://input");$json= json_decode($post,true);$appid = Yii::app()->params['app_id'];  //appId$secret = Yii::app()->params['secret']; //appSecret$code = $json["code"];$url = 'https://api.weixin.qq.com/sns/jscode2session?appid='.$appid.'&secret='.$secret.'&js_code='.$code.'&grant_type=authorization_code';$curl = curl_init();curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);curl_setopt($curl, CURLOPT_TIMEOUT, 500);// 为保证第三方服务器与微信服务器之间数据传输的安全性,所有微信接口采用https方式调用,必须使用下面2行代码打开ssl安全校验。curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);curl_setopt($curl, CURLOPT_URL, $url);$res = curl_exec($curl);curl_close($curl);$json_obj = json_decode($res, true);//  存储登录态$session = Yii::app()->session;$_3rd_session=$this->Get3rdsession(16);$session[$_3rd_session]=array('openid'=>$json_obj["openid"],'session_key'=>$json_obj["session_key"]);//存储用户信息$data['isSaveSuccess']=$this->addUserInfo($json["encryptedData"],$json["iv"],$json_obj['session_key']);//返回登录态用于用户识别$data['_3rd_session'] =$_3rd_session;echo CJSON::encode($data);}

第二步 保存并返回登录态

从上面的时序图可以知道,我将登录态定义为一串随机序列3rd_session,用其作为keyopenid为value,对应存入session中,传回小程序的就是一段随机序列3rd_session用其代替openid来区别用户。这样即使这段序列被他人获取也没关系。
那么这段3rd_session如何得到,见以下代码:

       //随机数作为keypublic  function Get3rdsession($len){$fp = @fopen('/dev/urandom', 'rb');$result = '';if ($fp !== FALSE) {$result .= @fread($fp, $len);@fclose($fp);} else {trigger_error('Can not open /dev/urandom.');}// convert from binary to string$result = base64_encode($result);// remove none url chars$result = strtr($result, '+/', '-_');return substr($result, 0, $len);}

随机序列的产生使用的linux系统的一个随机数urandom(mac也有,没有可以自定义生成,具体自行搜索),据说是随机性很高,官方推荐,可以自行搜索了解。
最后将3rd_session保存到小程序的缓存中。

第三步 存储用户信息

通过session_key+encryptedData+iv解密得到用户信息存入数据库
存储用户信息到数据库代码:

      //保存登录用户信息public function addUserInfo($encryptedData, $iv, $sessionKey){$data=$this->decryptData($encryptedData, $iv, $sessionKey);$s='';if($data){$user_info=new UserInfo();$user=UserInfo::model()->find("user_openid='".$data['openId']."'");if(empty($user)){$user_info->isNewRecord=true;$user_info->user_img=$data['avatarUrl'];$user_info->user_nickname=$data['nickName'];$user_info->user_gender=$data['gender'];$user_info->user_province=$data['province'];$user_info->user_city=$data['city'];$user_info->user_country=$data['country'];$user_info->user_openid=$data['openId'];$user_info->user_loginTime=$data['watermark']['timestamp'];$s=$user_info->save();}else{$user->user_img=$data['avatarUrl'];$user->user_nickname=$data['nickName'];$user->user_gender=$data['gender'];$user->user_province=$data['province'];$user->user_city=$data['city'];$user->user_country=$data['country'];$user->user_loginTime=$data['watermark']['timestamp'];$s=$user->save();}}return $s;}

解密用户信息代码:

protected function decryptData($encryptedData, $iv, $sessionKey){$appid = Yii::app()->params['app_id'];if (strlen($sessionKey) != 24 || strlen($iv) != 24) {return false;}$aesKey = base64_decode($sessionKey);$aesIV = base64_decode($iv);$aesCipher = base64_decode($encryptedData);$result = openssl_decrypt($aesCipher, "AES-128-CBC", $aesKey, 1, $aesIV);$data = json_decode($result, true);if(empty($data) || $data['watermark']['appid'] != $appid) {return false;}return $data;}

最后一步 使用3rd_session标识用户,获取用户对应的openid

在这里我踩了一个很大坑,研究了好久。首先不同浏览器使用的session是不一样的,所以我在一个浏览器将数据存入session,我在另外一个浏览器用相同的key并不能取到存入的那个值。
到小程序的时候更发现小程序是没有Cookie的,所以调用接口的时候所有的session都是不一样的,所以我存到session的值,再次调用其他接口时根本就取不到。
解决办法:保存小程序的sessionId到小程序的缓存,在调用接口时,将sessionId加入到wx.request()的head中,就能使用同个session从而取到值了。
获取sessionId: 上面小程序登录代码里面有就一句

wx.setStorageSync("sessionid", res.header["Set-Cookie"]);

使用时只要在有用到sesioon的wx.request()的head这样写:

wx.request({header: {'content-type': 'application/json', // 默认值'cookie': wx.getStorageSync("sessionid")},url: app.globalData.url + "GetAddress&user_identify=" + wx.getStorageSync('_3rd_session'),method: "Get",success: (res) => {}})

在服务器中这样用3rd_session获得openid:

               $session = Yii::app()->session;$openid=$session[$user_identify]['openid'];

这里还有一点就是sessionId是会过期的也就是session,所以我定义了一个全局方法refresh定时更新(这里我是定停留一个页面20分钟重新自动登录),在需要用到session的页面的OnLoad方法调用这个方法refresh。
在app.js定义:

refresh:function(){let that=this;setInterval(that.reLogin,20*60*1000);},reLogin:function(){var that = this;var code = '',openid = '', encryptedData = '', iv = '';// 查看是否授权wx.getSetting({success: function (res) {if (res.authSetting['scope.userInfo']) {wx.getUserInfo({success: function (res) {//传入用户信息that.globalData.userInfo = res.userInfo;encryptedData = res.encryptedData;iv = res.iv;wx.login({//获取codesuccess: function (res) {code = res.code //返回codewx.request({url: that.globalData.url + 'WxLogin',data: {code: code,encryptedData: encryptedData,iv: iv,},method: 'Post',header: {},success: function (res) {//返回登录态(用户标识)保存伪cookiewx.setStorageSync('_3rd_session', res.data._3rd_session);wx.setStorageSync("sessionid", res.header["Set-Cookie"]);console.log("重新登录了");},fail: function (err) {console.log('error', err);}})}})}});}}})},

调用:

onLoad: function (options) {var that = thisthis.loadData(0);app.refresh();},

这样用户登录就大功告成了,比较详细;以上都是一个人慢慢摸索得出的,不能说百分百正确,有什么错误请直接指出。研究不易,请多多支持。