您的位置 首页 php

暴走状态!小程序用户UnionID的获取及登录状态维护——给你代码

2fb535a441ba43e6a29a22525e1744dc

气得我啃键盘

赶在下班前把我们的鲲豆荚小程序第一版提交了审核,趁现在还在有脑回路的状态下记下踩的坑和与之对应的解决策略。

之前的文章我们提过,微信生态体系下,同一个用户在不同的小程序中的 OpenID 都是不同的。因为我们需要识别出使用微信登录和小程序登录是同一个人,就不能使用openId了,微信也提供了这么一个标识: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('#39;.$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,嗯?

5bdf00de54594c198e3e69bcfd6cd65b

带着这个问题再看了不知几多遍官方文档,原来是UnionID下发是有很多条件限制的,以下贴图我很难表述:

3f0519c663f04db9ac12b92a5978a637

86829fadd47647cb9151a2b47315a62a

此时我内心是五彩斑斓的,就像那打翻了吹彩虹屁的糖罐,实话不怕告诉你,胡里花哨东西太多,我都看不懂啊!不管怎么样,选项太多,只好祭出排除大法:首先排除4,5,6三个方案,因为我们没有使用支付和云服务,第2和第3有点坑爹的意思,必须首先关注公众号才能获取UnionID?这个操作局限性太大,难道小程序的入口仅仅只有关注绑定的公众号才能进入吗?

ed890be146194b85a0f387d185618d6f

什么乱七八糟的规则,很生气,生了一天的闷气!那就只剩第1条路去走一走了!

从解密数据中获取UnionID

在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           }         });       }    });  }})  

服务器端在做解密校验之前,需要下载小程序的解密库,下载地址:。本例使用的是世界上最好的语言,校验代码如下:

 <?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('#39;.$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信息如下:

f049352be0ca4bbabc7ec4a61c2edf79

后续服务器端因数据库和登录实现各异,仅提供思路:既然我们获取到了unionId,应该将这个unionId和数据库用户进行比对,如果没有则作为新用户插入,接着需要颁发一个登录凭证token返回给小程序,小程序将这个token保存到本地,再后续发起需要登录凭证的API请求时带上。

实测并不完美的方案

在我们实际测试中,点击登录按钮后会经常出现“登录失败,请重试”的提示,接连再点又能成功登录。做程序这行呢,有bug并不可怕,怕就怕时而正常时而癫狂,同样的代码,同样的操作,为什么会结出不同样的果?为什么要这么秀?

d98671cc884e490fae655618bb4648e0

咆哮帝上身

就是说在解密的时候出错了,调试后发现返回的是-41003,对照error code说明,是“aes解密失败”的意思。但为什么大部分情况下又可以成功解密?难道是算法有问题?我用的是官方解密类库啊,这个应该不会吧,那就是获取的session_key有问题?可这个session_key也是我拿着code从微信服务器返回的啊,都是微信给的,这个锅我不要背。

f20091f63047461a88dbca49cc37e4da

谁还不是个宝宝

再仔细翻看官方文档对session_key的说明,原来是有个时效性:

6ad40d1b59274e5bac52057981c053c3

“最短机制”,“session_key有效期不告诉你”,“频繁使用小程序,session_key有效期越长”,天哪,这是人写吗,这么模棱两可的话都写在官方文档里,一头雾水有没有?好吧,按照第3条说的,在每次调用wx.login()前,先调用wx.checkSession,但每次都是成功的,从来就没有出现fail的情况,所以问题依旧。一度陷入不知所措的地步,小编问什么时候能交稿,我说被一个问题卡很久了,没错就是这个,整整一个下午,毫无进展。

退一步海阔天空

e87e120215d6484194eb62852c9aa34c

宝宝不开心

回过头重新思索了下整个登录流程:我们核心是需要获取到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直接调用本接口进行身份伪造。但毕竟这不是最优解决方案,微信登录流程优化上本应该能做到更好,说不定哪天就优化了呢……也说不定。

3af474ce87cf4080ba6f285ca8bb023b

满脸高兴


给你代码往期回顾:

文章来源:智云一二三科技

文章标题:暴走状态!小程序用户UnionID的获取及登录状态维护——给你代码

文章地址:https://www.zhihuclub.com/42214.shtml

关于作者: 智云科技

热门文章

网站地图