评论

手机号解密失败?扫盲帖+解决方案

解密失败扫盲帖

一、之前的解密流程,会偶现“解密失败”,步骤如下:

1、点击 ‘getPhoneNumber’ 的按钮,弹出授权界面

2、调用 wx.checkSession 进行检查,success 的话就发送授权手机号解密接口;fail 的话就进行 wx.login 获取 code 后请求后端获取 openid/sessionKey,并将两个值缓存到本地,然后再进行手机号解密。

二、粗暴方式解密流程,会偶现“解密失败”,步骤如下:

1、点击 ‘getPhoneNumber’ 的按钮,弹出授权界面

2、调用 wx.login 获取 code 后请求后端获取 openid/sessionKey,然后进行手机号解密


探讨微信解密机制(个人理解,如有不对,请指出哟),如下:

1、点击 ‘getPhoneNumber’ 的按钮,弹出授权界面时,在微信后台会将数据进行加密,此时用于加密的 sessionKey-0 是微信后台已存的 sessionKey,这个 sessionKey 有可能是之前其他业务生成的,比如 wx.login(会刷新微信后台的 sessionKey)。

2、按照如上错误的步骤,进行 wx.login 获取 code 再获取 sessionKey-1,由于 wx.login 会刷新微信后台的登录状态,此时新生成的 sessionKey-1 和上一步中用于加密的 sessionKey-0 不是同一个,于是解密失败了。

3、重点:必须保持操作前后的 sessionKey 都是同一个,如果 wx.login 刷新了后台的 sessionKey,而之前的操作还是原来的 sessionKey,则会失败。


必现方式一(解密失败),如下:

1、正常授权解密后,关闭小程序一段时间,测试的时候大概 30-40 分钟左右,后台 sessionKey 过期了

2、重新打开小程序走之前的步骤,出现“解密失败”,然后第二次进行授权解密时,又成功了

必现方式二(解密失败),如下:

1、清理缓存后,点击授权登录(此时后台的sessionKey为空,所以无论怎么做登录和检查,都会解密失败),弹出界面后调用 wx.login 进行登录获取 sessionKey

2、授权解密,失败

3、再次点击授权登录(此时会获取第一步中登录生成的 sessionKey,与未过期的sessionKey一致),则进行解密,成功。


解决方案,如下:

基于对上面机制的理解,wx.login 方式获取的 sessionKey 必须在唤起授权弹窗前进行,必须保证授权弹窗时获取的 sessionKey 和 本地未过期的sessionKey是同一个。

1、在 app.js 的 onLaunch 中,先进行 wx.checkSession。success,则进行后续代码执行;如果 fail,则进行 wx.login 重新获取 sessionKey,后端同学将 sessionKey 存储至数据库,openid 是唯一的,可以作为主键。此时,会先于任何操作之前生成一个 sessionKey。

2、唤起授权界面,点击“允许”后,将 openid+iv+encryptedData 传给后端进行解密。

代码如下(IDE复制后没发改样式,const 等变量可能看不清,可以复制到自己的编辑器中查看哈):

然后在 onLaunch 中调用 onLogin.checkSession 进行前置 session 检查和获取

export const onLogin = {
  // 检查 session 是否有效
  checkSession(cb = () => {}) {
    const _this = this;

    // 检查登录态是否可用
    wx.checkSession({
      success () {
        console.log('session success');
        // session_key 未过期,并且在本生命周期一直有效
        const { openid, session_key } = wx.getStorageSync('session');
        cb(openid, session_key);
      },
      // 登录态失效了
      fail () {
        console.log('session fail');
        _this._wxLogin((openid, session_key) => {
          cb(openid, session_key);
        });
      }
    });
  },
  // 微信获取 code 进行解密
  _wxLogin(cb = () => {}) {
    // 将 js_code 发送给后端获取 openId 和 sessionKey,并进行缓存
    wx.showLoading({ mask: true });
    wx.login({
      success: ({ code }) => {
        // 将 js_code 发送给后端获取 openId 和 sessionKey,并进行缓存
        request({
          url: `/wx/open/getSessionKey.json?code=${code}&clientIdentity=${CONFIG.clientIdentity}`,
          onSuccess: ({ openid, session_key }) => {
            wx.setStorageSync('session', { openid, session_key });
            cb(openid, session_key);
          },
          complete: () => {
            wx.hideLoading();
          }
        })
      }
    });
  },
}
最后一次编辑于  2020-11-05  
点赞 1
收藏
评论

7 个评论

  • 侠客有情剑无情
    侠客有情剑无情
    2021-05-13



    我这里有一种解决方案, 前提是当用户登录过以后,不能再重复登录的分一下几个步骤:

    data: {
       code: ''  // 定义好code
    }
    
    // 1. 页面onLoad的时候调用一下获取code
    
    onLoad: async function (options) {
        // 先调用login,在获取手机号的回调方法中取检验登录状态
        const { code } = await wx.pro.login()
        this.setData({ code })
      },
    


     // 2. 避免code失效,检查登陆态,获取最新的code
    async getLoginCode() {
      let code = ''
      try {
        await wx.pro.checkSession()
        code = this.data.code
      } catch (error) {
        console.log('登录态失效');
        const res = await wx.pro.login()
        code = res.code
      }
      return code
    }
    



    //3. 调用我们自己服务器的登录接口,换取token
    async getToken(e) {
        // 这里是避免重复登录的loading状态
        if (this.data.wxLoginLoading) { return }
        this.setData({ wxLoginLoading: true })
        // 调用login获取微信code
        const code = await this.getLoginCode()
        const { encryptedData, iv } = e.detail
        try {
          const res = await newFly.post('/user/login', { code, encryptedData, iv })
          const { token, phoneNumber } = res.data.data || {}
          console.log('phone: ', phoneNumber);
          console.log('token: ', token);
        } catch (err) {
          // 出现错误兜底
          const {code} = await wx.pro.login()
          this.setData({code})
          wx.showToast({ title: '登录失败', icon: 'none' })
        } finally {
          this.setData({wxLoginLoading: false})
        }
      }
    


    上面代码有一个出现错误兜底功能,如果我们的服务器返回错误,就会走catch代码里。并且我们的服务器发送的code已经被微信服务器使用过的时候。再次登录就会报错code used因为一个code只能用一次。所以在出现错误的时候重新获取一下code,然后出现错误的时候就可以重新登录了。



    大家有什么需要讨论的一起讨论一下。

    2021-05-13
    赞同 1
    回复 1
    • 阿白
      阿白
      2022-09-28
      你好,我也遇到了同样的问题,以上代码可以彻底解决解密报错的问题吗?
      2022-09-28
      回复
  • 浅行
    浅行
    2021-04-25

    但是,我使用的是将code及 encryptedData 和 iv 一块传给后端,然后后端使用code获取openId和SessionKey,再拿获取的SessionKey 、iv和encryptedData 解密拿到手机号,但依旧会出现解密失败的问题。 按逻辑来看,此时的SeesionKey应该是最新的才对,为什么还会解密失败呢?

    2021-04-25
    赞同 1
    回复 4
    • 落雨
      落雨
      2021-05-08
      请问解决了么?我现在也是如此,将code和encryptedData、iv一起传给后端,后端先使用code拿openid和sessionKey,之后在用sessionkey、iv、encryptedData去解密手机号,还是会存在解密手机号失败
      2021-05-08
      1
      回复
    • 王明
      王明
      2021-06-10
      我也是这样的 也会出现偶尔的失败 ,本地代码复现不了 用户有反应了,怎么办呢
      2021-06-10
      回复
    • 沧海的雨季
      沧海的雨季
      2021-06-16
      请问这个问题解决了吗?
      2021-06-16
      回复
    • 小名叫超超
      小名叫超超
      2021-07-01
      可能微信服务端的sessionKey还来不及更新
      2021-07-01
      回复
  • 疯狂电脑+10086
    疯狂电脑+10086
    2020-11-05

    也就是后端要对每个用户维护一个sessionKey ,存到数据库中?

    2020-11-05
    赞同 1
    回复 2
    • 小坏จุ๊บ
      小坏จุ๊บ
      2020-11-05
      可以选择后端存储,也可以选择前端存入缓存中,只要保证 sessionKey 没过期就可以啦,解密的时候,将 sessionKey 和 openId 传给后端就好了
      2020-11-05
      回复
    • 曾威
      曾威
      2021-03-09回复小坏จุ๊บ
      还是不稳定,前后端分工的话,前端哪里来个wx.login,后端的sessionKey就废了。。。。。
      2021-03-09
      回复
  • 含光
    含光
    2021-05-08

    我在前端解密手机号也会出现解密手机号失败,解密方式如下:

    1、点击 ‘getPhoneNumber’ 的按钮,弹出授权界面

    2、调用 wx.login 获取 code 后请求后端获取 openid/sessionKey

    3、前端解密手机号

    偶尔会出现解密手机号失败,但是再次重复上面3个步骤解密手机号又可以了,请问是什么原因导致的呢?


    2021-05-08
    赞同
    回复 2
    •      
           
      2021-07-02
      解决了吗,我们前端必须请求两次后端才会解密成功,第一次就解密失败
      2021-07-02
      回复
    • 含光
      含光
      2021-07-09回复     
      没有,我现在把解密手机号放到前端,也是要解密两次
      2021-07-09
      回复
  • 含光
    含光
    2021-04-15

    话说我在解密手机号的前一个步骤获取sessionKey再传给后端解密能不能一步到位,这样sessionkey是不是就是唯一的值了呢?不再使用checkSession这个函数了,因为他无论成功还是失败都是success,我觉得这个思路应该是正解。有疑问请说出理由

    2021-04-15
    赞同
    回复
  • 古寅生
    古寅生
    2021-03-31

    上述逻辑中,也会一定概率出现授权手机号失败的情况。

    由于code换sessionKey需要到服务端做请求,当onLaunch调用request超时,或者response太慢的时候,假定1s以上才返回response,还没等storage存储sessionKey,客户在画面上可能操作了授权手机号的按钮,这样,也会导致授权失败。


    2021-03-31
    赞同
    回复 1
    • 侠客有情剑无情
      侠客有情剑无情
      发表于移动端
      2021-05-13
      可以在请求成功之后传一个回调或者加一个标识,只有成功返回后才能请求登陆接口
      2021-05-13
      回复
  • 幻
    2020-10-29

    您好,有具体关于后端解密的代码吗?最好是java版本的

    2020-10-29
    赞同
    回复 2
登录 后发表内容