赶不才班前把我们的鲲豆荚小程序初版提交了审核,趁现在还在有脑回路的状态下记下踩的坑和与之对应的办理议方案略。
之前的文章我们提过,微信生态体系下,同一个用户在不同的小程序中的OpenID都是不同的。由于我们须要识别出利用微信登录和小程序登录是同一个人,就不能利用openId了,微信也供应了这么一个标识:UnionID。官方对UnionID的机制解释如下:
如果开拓者拥有多个移动运用、网站运用、和"大众年夜众帐号(包括小程序),可通过 UnionID 来区分用户的唯一性,由于只假如同一个微信开放平台帐号下的移动运用、网站运用和公众帐号(包括小程序),用户的 UnionID 是唯一的。换句话说,同一用户,对同一个微信开放平台下的不同运用,UnionID是相同的。
爱之初体验
我们希望不止能识别用户的唯一性,还希望用户可以把根本开放数据授权给我们:头像和昵称。微信供应了一个wx.login()方法,按照以往第三方登录开拓履历,调用这个方法就可以完成授权登录了,可是这个只能返回一个5分钟时效的code,须要传回自己做事器调用微信接口auth.code2Session来获取OpenID和UnionID。好吧,我们按照步骤先实践一遍,当时源码是这样的:
<!-- wxml --><button open-type="getUserInfo" bindgetuserinfo="login">登录</button>
js:
Page({ login(e) { wx.login({ success: res => { getApp().query({ api: 'user/thirdparty/mini:login', param: { code: res.code } }); } }); }})
做事器端接口 user/thirdparty/mini:
<?phpreturn [ / 第三方登录,返回token @param string $code 登录code @return string token / 'login' => function($code) { $api = 'jscode2session'; $params = [ 'appid' => '<<APP ID>>', 'secret' => '<<APP SECRET>>', 'js_code' => $code, 'grant_type' => 'authorization_code' ]; // 获取session_key $response = file_get_contents('https://api.weixin.qq.com/sns/'.$api.'?'.http_build_query($params)); $response = json_decode($response); if(isset($response->errcode)) throw new Exception($response->errmsg); print_r($response); }];
该当是没有问题的,对不对?但是!
!
!
实际打印出来的$response中,只有openid和session_key,便是没有我们最想要的unionid,嗯?
带着这个问题再看了不知多少很多多少遍官方文档,原来是UnionID下发是有很多条件限定的,以下贴图我很难表述:
此时我内心是五彩斑斓的,就像那打翻了吹彩虹屁的糖罐,实话不怕见告你,胡里花哨东西太多,我都看不懂啊!
不管怎么样,选项太多,只好祭出打消大法:首先打消4,5,6三个方案,由于我们没有利用支付和云做事,第2和第3有点坑爹的意思,必须首先关注公众年夜众号才能获取UnionID?这个操作局限性太大,难道小程序的入口仅仅只有关注绑定的公众年夜众号才能进入吗?
什么乱七八糟的规则,很生气,生了一天的闷气!
那就只剩第1条路去走一走了!
在open-type="getUserInfo"的button组件中,我们绑定了getUserInfo事宜回调给login方法,e.detail.encryptedData有我们所须要的所有数据,但是encryptedData中的数据经由了加密,须要传回做事器解密,解密须要用到加密算法初始向量e.detail.iv。我们把login方法稍作修正,增加两个参数:
Page({ login(e) { wx.login({ success: res => { getApp().query({ api: 'user/thirdparty/mini:login', param: { code: res.code, encrypted: e.detail.encryptedData, iv: e.detail.iv } }); } }); }})
做事器端在做解密校验之前,须要下载小程序的解密库,下载地址:https://res.wx.qq.com/wxdoc/dist/assets/media/aes-sample.eae1f364.zip。本例利用的是天下上最好的措辞,校验代码如下:
<?phpreturn [ / 第三方登录,返回token @param string $code 登录code @param string $encrypted 通过getUserInfo获取的encryptedData @param string $iv 加密初始向量 @return string token / 'login' => function($code, $encrypted, $iv) { $api = 'jscode2session'; $params = [ 'appid' => '<<APP ID>>', 'secret' => '<<APP SECRET>>', 'js_code' => $code, 'grant_type' => 'authorization_code' ]; // 获取session_key $response = file_get_contents('https://api.weixin.qq.com/sns/'.$api.'?'.http_build_query($params)); $response = json_decode($response); if(isset($response->errcode)) throw new Exception($response->errmsg); require '/path/to/wxBizDataCrypt.php'; // 引入小程序解密类库 $pc = new WXBizDataCrypt('<<APP ID>>', $response->session_key); $errCode = $pc->decryptData($encrypted, $iv, $user); if(0 != $errCode) throw new Exception('登录失落败,请重试'); print_r($user); }];
不出意外的话,能顺利打印出的$user信息如下:
后续做事器端因数据库和登录实现互异,仅供应思路:既然我们获取到了unionId,该当将这个unionId和数据库用户进行比对,如果没有则作为新用户插入,接着须要颁发一个登录凭据token返回给小程序,小程序将这个token保存到本地,再后续发起须要登录凭据的API要求时带上。
实测并不完美的方案在我们实际测试中,点击登录按钮后会常常涌现“登录失落败,请重试”的提示,接连再点又能成功登录。做程序这行呢,有bug并不可怕,怕就怕时而正常时而癫狂,同样的代码,同样的操作,为什么会结出不同样的果?为什么要这么秀?
咆哮帝上身
便是说在解密的时候出错了,调试后创造返回的是-41003,对照error code解释,是“aes解密失落败”的意思。但为什么大部分情形下又可以成功解密?难道是算法有问题?我用的是官方解密类库啊,这个该当不会吧,那便是获取的session_key有问题?可这个session_key也是我拿着code从微信服务器返回的啊,都是微信给的,这个锅我不要背。
谁还不是个宝宝
再仔细翻看官方文档对session_key的解释,原来是有个时效性:
“最短机制”,“session_key有效期不见告你”,“频繁利用小程序,session_key有效期越长”,天哪,这是人写吗,这么模棱两可的话都写在官方文档里,一头雾水有没有?好吧,按照第3条说的,在每次调用wx.login()前,先调用wx.checkSession,但每次都是成功的,从来就没有涌现fail的情形,以是问题依旧。一度陷入不知所措的地步,
回过分重新思虑了下全体登录流程:我们核心是须要获取到UnionID,得到后就统统好办了。经由测试,第一次通过授权登录是不会涌现解密失落败的情形,那何不在用户第一次登录时记录下UnionID,在后续登录直接回传给做事器完成二次登录,这样无需经由解密环节,也就不会涌现因session_key古怪的失落效机制引起的问题。
我们在app.js中加入一个方法:id(),用来获取和设置UnionID:
App({ / 获取/设置用户小程序内的unionId @param string unionId / id(value = null) { return value ? wx.setStorageSync('id', value) : wx.getStorageSync('id'); }});
再次修正login()方法:
Page({ login(e) { let unionId = getApp().id(); if(unionId) { // 已缓存过用户唯一识别信息 getApp().query({ api: 'user/thirdparty/mini:relogin', param: { unionId: unionId } }) } else { // 首次授权登录 wx.login({ success: res => { getApp().query({ api: 'user/thirdparty/mini:login', param: { code: res.code, encrypted: e.detail.encryptedData, iv: e.detail.iv } }).then(data => getApp().id(data.unionId)); } }); } }})
我们在首次授权登录(用户移除了小程序后再次进入须要重新登录授权的也算首次,由于unionId也一并被打消了)后,做事器端会返回一个unionId字段,我们利用getApp().id()保存到小程序客户端,这样下次用户再次登录时会调用新的接口relogin:
<?php// user/thirdparty/minireturn [ 'relogin' => function($unionId) { // @todo 检讨当前token是否未被更换掉,设置登录状态,返回新颁发的token }];
把稳这里为了安全起见,须要将自身末了一次token(纵然过期但仍未被更换)带上去做事器检讨,以此为根本才能将颁发新的token。这样做是为了防止恶意用户拿到了别人的UnionId直接调用本接口进行身份假造。但毕竟这不是最优办理方案,微信登录流程优化上本该当能做到更好,说不定哪天就优化了呢……也说不定。
满脸高兴
给你代码往期回顾:
给你代码:leetcode题目加小技巧
给你代码:小程序内容滚动与导航栏自动高亮联动
给你代码:小程序引入icon的三种办法