阅读 1670
小程序登录授权+获取手机号踩坑记

小程序登录授权+获取手机号踩坑记

最近在做toC的小程序, 之前做过的东西又重新捡了起来。 但是发现之前踩过的一些坑,又被自己重新踩了一遍。

脑子真的是记不住啊~ 忙里抽闲赶紧做个笔记。

重点笔记:获取微信用户绑定的手机号,需先调用wx.login接口。

原理:小程序获取到的手机号信息为微信服务器加密后的信息。 微信服务器会根据wx.login颁发的临时code对应的sessionKey来加密手机号信息。 在开发者服务器使用 code 换取 sessionKey , 比对换取的sessionKey与加密信息时使用的sessionKey一致才可解密成功。 

注意:服务器使用 code 换取的 sessionKey 不是加密时使用的 sessionKey,导致解密失败。建议开发者提前进行 login。

一、 登录授权需求

因业务限制,用户登入小程序前需先授权手机号才能进入小程序。

前端在App.js入口文件中读取本地存储, 判断是否有手机号,有则直接刷新用户信息登入,无则需走登录授权流程登入。

二、 19年开发的公司内部小程序,登录授权设计。

后台设计两个接口, 登录注册接口、更新用户信息接口。

登录注册接口逻辑:

1、根据入参code,调用微信 auth.code2Session 接口换取openId、session_key。

2、换取成功,查询入参openId在用户表中是否存在,存在则更新session_key, 不存在则创建用户并存储openId、session_key。 处理后将openId返给前端。

前端调用:

登录页onShow生命周期中,调用该接口,并将返回数据在页面state记录。

onShow: function () {
    let that = this;
    // 判断是否缓存手机号
    that.data.purePhoneNumber = wx.getStorageSync("purePhoneNumber");
    if (that.data.purePhoneNumber == '') {
      wx.login({
        success(res) {
          WXrequest.post({
            url: '/code2Session',
            data: {
              'jsCode': res.code
            }
          }).then(res => {
            wx.setStorageSync('openid', res.data.openid);
            that.data.openid = res.data.openid;
          });
        }
      });
    } else {
      wx.showLoading({
        title: '登录中' // 数据请求前loading
      })
      wx.switchTab({
        url: '../mine/index'
      })
    }
  }
复制代码

更新用户信息接口逻辑:

1、根据入参openId去用户表中查询session_key, 并结合入参encryptedData、iv解密获取手机号。

2、解密到手机号后,更新用户信息并将用户信息返给前端。

前端调用:

用户主动触发登录按钮, 获取手机号后调用。

getPhoneNumber: function (e) {
    let that = this;
    if (e.detail.errMsg == 'getPhoneNumber:ok') {
      WXrequest.post({
        url: '/getWXUserPhone',
        data: {
          'encryptedData': e.detail.encryptedData,
          'openid': that.data.openid,
          'iv': e.detail.iv,
        }
      }).then(res => { 
       // 存用户手机号及用户信息
        wx.setStorageSync('purePhoneNumber', res.data.purePhoneNumber);
        wx.setStorageSync('userInfo', res.data);
        // 跳转首页
        wx.showLoading({
          title: '登录中'
        })
        setTimeout(function () {
          wx.switchTab({
            url: '../mine/index'
          })
        }, 1000)
      })
    }
  }
复制代码

注:code有效期为五分钟,在登录页onShow生命周期中获取code, 如果用户打开页面5分钟后再授权手机号,则会解密失败。需要做优化处理。

三、 最近开发的toC小程序登录授权设计。 

后台同事将小程序登录+获取手机号设计为一个接口,叫登录授权接口。

接口逻辑:

1、根据入参code先去调用 auth.code2Session 接口换取openId、session_key。

2、根据 appId、入参encryptedData、上一步换取的session_key,进行数据解密,获取手机号。

3、查询openId在用户表中是否存在,进行逻辑处理。

         存在,判断该用户手机号与解析出来的手机号是否一致,一致接口返回用户信息;不一致,更新手机号,将之前的手机号做历史记录。

         不存在, 直接创建用户,返回用户信息。

前端调用:

因考虑到code,有效期为五分钟。 在登录页onShow生命周期中获取code, 无法保证用户一定会在五分钟内授权手机号。

所以在获取手机号成功后再调用wx.login()获取code,  再去调用登录授权接口。

问题:

先获取手机号再调用wx.login(),  造成后台解析手机号失败,报错:session_key失效。

根据以往经验,直接开始撸代码了,时间太久忘了一些注意事项。报错后才重新读了一遍文档,文档中明确提示:获取微信用户绑定的手机号,需先调用wx.login接口。

注:阅读下面文档, 了解微信对开放数据校验与解密, 对获取手机号之前为什么要调用wx.login接口有更深刻的印象。

https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html

解决方案:

登录页onShow生命周期中每隔几分钟,重新刷一下登录态。 在页面state记录状态, 获取手机号成功后一起传给后台。

componentDidShow() {
    // 隐藏房子
    Taro.hideHomeButton();
    // code 用户登录凭证(有效期五分钟), 停留在当前页面每隔两分钟,重新刷新登陆态,否则后台解析session_key会失效
    Taro.login({
      success: (res) => {
        this.setState({
          code: res.code,
        });
      },
    });
    this.timer = setInterval(() => {
      Taro.login({
        success: (res) => {
          this.setState({
            code: res.code,
          });
        },
      });
    }, 60000 * 2);
}



// 获取手机号
getPhoneNumber(e) {
    if (e.detail.errMsg.indexOf("getPhoneNumber:fail") != -1) {
      if (e.detail.errMsg.indexOf("user cancel") != -1) {
        toast("请不要重复点击、以免取消微信授权");
      } else {
        toast("允许授权将获得更好的服务");
      }
    } else {
      this.setState({
        loading: true,
      });
      Taro.request({
        url: `${config.baseUrl}/user`,
        method: "POST",
        header: { "Content-Type": "application/json" },
        data: {
          code: this.state.code,
          iv: e.detail.iv,
          encryptedData: e.detail.encryptedData,
        },
        success: (res) => {
          const redData = res.data.data || {};
          if (res.data.code != 0) {
            toast(res.data.msg);
          } else {
            Taro.setStorageSync(`${config.env}openId`, redData.openId);
            Taro.setStorageSync(`${config.env}userId`, redData.userId);
            Taro.setStorageSync(`${config.env}phone`, redData.phone);
            this.props.dispatch({
              type: "User/setUser",
              payload: {
                userInfo: redData,
              },
            });
            this.setState({
              loading: false,
            });
           }
        },
        fail: (err) => {
          this.setState({
            loading: false,
          });
          toast(err);
        },
      });
    }
  }


// DOM
 <Button
     type="primary"
     disabled={this.state.loading}
     loading={this.state.loading}
     className={styles.phone}
     openType="getPhoneNumber"
     onGetPhoneNumber={(e) => this.getPhoneNumber(e)}
    >
      {this.state.loading ? "登录中" : "授权手机号"}
  </Button>
 
复制代码

四、进一步了解微信登录授权机制原理。

踩坑后重新研究了OAuth 2.0 安全协议, 小程序登录授权、开放数据校验与解密等流程及背后原理。

抽时间整理在后面的笔记中...

文章分类
前端
文章标签