收藏
回答

小程序验证签名(登录)的流程(含官方解答的最佳实践)

小程序审核突然没通过,理由如下:


这个问题开发过程中自己确实遇到过,几率性的,一般第一次不行,第二次肯定可以了,但是不是一开始写小程序就有的,不知道什么时候开始就这样了,验证的逻辑都是按照官方的,从来没有改变过。然后上社区一搜,很多类似的问题,如下图所示。



看了下这个问题,第一次验证签名如下:

  1. 小程序端通过wx.login成功后获取的code

  2. rawdata,这个我都是同一用户登录,前后信息没啥变化

  3. 通过1中的code,后端调用api获得的session data,其中openid肯定同一用户每次也都一样的,session_key如果过期,那么第一次和第二次理论应该是不一样的。(但实际情况前后两次是一致的,具体可参见下图)

  4. 小程序端获取到的用户的签名

  5. 后端通过session key校验出来的签名。

很明显,4和5不一致,校验失败。接下来是第二次交验:


还是同样的逻辑顺序。

  1. 小程序端通过wx.login成功后获取的code。很明显,code跟第一次是不一样的,另外根据官方文档描述,因为又重新调用了wx.login,会导致session_key过期。(这似乎说明code发生变化也是对的,因为按推测,seesionkey应该也发生了变化,否则怎么叫“被更新”)请看下图官方文档说明:

  2. rawdata,这个我都是同一用户登录,前后信息没啥变化

  3. 根据1中的官方描述,奇怪的现象就发生了,在后端根据新的code,获取的session data,很明显session key还是第一次是一样的,也就是说,我重新调用了wx.login, code是变了,但是session key却和第一次保持一致的。

  4. 小程序端获取到的用户的签名

  5. 后端通过session key校验出来的签名。因为用的是同样的rawdata,同样的session key,所以两次校验的结果是一样的,但是第二次4中,小程序端获取的签名是跟此次校验结果是一致的。

所以问题就来了,这问题到底出在什么地方?似乎官方文档描述的就有问题,还是我本身的逻辑顺序有问题?请官方指教,谢谢。

最后一次编辑于  2018-09-05
回答关注问题邀请回答
收藏

8 个回答

  • 小程序/小游戏开发-Link
    小程序/小游戏开发-Link
    置顶回答2018-09-04

    code 的作用只是用来换取 session_key,从你提供的信息来看,不会是 code 的问题,只可能是 session_key 的问题。有个问题需要注意:

    验签使用的 session_key,必须和小程序端获取到的签名是配对。因此这里需要你确认下,获取 3 和 4 的时序关系是怎样的?预期应该是这样的逻辑:

    1. 获取 3(wx.login + jscode2session 拿到 session_key)

    2. 获取 4(wx.getUserInfo 拿到 signature)

    3. 使用 1、2 拿到的结果来验签


    如果你的服务器缓存了 session_key,在 session_key 未过期的情况下(可用 wx.checkSession 确认),可以跳过 1 。假设你用的是很久以前拿到的 signature + 刚获取到的 session_key 来验签,那就有可能不通过。


    2018-09-04
    有用 2
    回复 23
    • zluo
      zluo
      2018-09-04

      非常感谢您的回答,那问题应该就是我步骤错了,我是先wx.getUserInfo 拿到 signature,然后再wx.login + jscode2session 拿到 session_key,最后才验签的,您说的1,2我反掉了。非常感谢。

      2018-09-04
      回复
    • zluo
      zluo
      2018-09-04

      实在不好意思,对于您的解答我还是有个疑问,需要麻烦问下。就是您说的1和2步骤,我不知道会不会出现这样的情况,就是我完成1步骤后,出于某种原因(因为至少目前开发者是没法获知以及控制sessionkey的有效期的),sessionkey过期了,然后现在开始执行步骤2,那么我拿到的用户签名肯定也是无效的,对吗?这样我去验签肯定会失败。而因为目前小程序wx.getUserInfo必须通过<button open-type="getUserInfo"></button>这样的形式,让用户主动点击才能获取对吗?就算是wx.authorize({scope: "scope.userInfo"}),也无法弹出授权窗口。这样问题就来了,为了验证签名,如果这种情况发生,是不是势必需要用户第二次点击这个按钮才能正确验签?如果这样的话,体验就很差了,还是说,我始终哪里没搞明白?麻烦帮忙解答下,谢谢。

      2018-09-04
      回复
    • Band
      Band
      2018-09-04回复zluo

      这个问题我来回答一下楼主。

      为了保证wx.getUserInfo不会因为session_key过期而失败。微信对于用户session_key过期的情况做了预生成session_key的机制。


      所以在你的流程“我是先wx.getUserInfo 拿到 signature,然后再wx.login + jscode2session 拿到 session_key,最后才验签的

      首先调用的wx.getUserInfo,签名所对应的session_key,对于小程序的老用户而言是不可预知的,因为用户之前的session_key可能有效也可能已经失效。这样可能会导致你签名校验失败。

      建议先调用 wx.checkSession先行获取session_key有效期(注意必须在wx.getUserInfo之前调用),并且在过期的情况下调用wx.login获取最新的session_key。

      2018-09-04
      2
      回复
    • 2018-09-05回复Band

      那么……我倒是有问题了

      如果说用户进入小程序的时候就调用了wx.login,经过了很长一段时间(此时session_key已经过期),用户点击了某个登陆按钮调起了getUserInfo,这种情况下就是无法解密的?

      因为现在的产品逻辑下,getUserInfo的时机不是一个可控的时机,毕竟是用户触发的,但是触发回调只有finish回调而没有will回调,也就是说获得了signature之后才知道自己的session有没有过期

      至于为什么不是可控时机,就要问你们自己废弃wx.getUserInfo的时候是怎么想的了……

      2018-09-05
      回复
    • 小程序/小游戏开发-Link
      小程序/小游戏开发-Link
      2018-09-05回复

      补充说明一点:wx.getUserInfo 并不是直接废弃,只是废弃了其首次获得用户授权的能力。如果已经通过 button 得到用户的授权(可通过 wx.getSetting 判断),后续都是可以直接用 wx.getUserInfo 接口不弹窗获取到用户信息的。

      2018-09-05
      回复
    查看更多(18)
  • 2018-09-06

    参考流程代码


    authPromise = new Promise(async (resolve, reject) => {
      while (!authorization.token) {
        try {
          const data = await WXP.login();
          let res;
          try {
            if (canAutoLogin) {
              res = await getToken(data.code);
            } else {
              throw Error('try user info');
            }
          } catch (e) {
            canAutoLogin = false;
            const set = await WXP.getSetting();
            if (!set.authSetting['scope.userInfo']) {
              throw Error('scope userInfo disabled');
            }
            const user = await WXP.getUserInfo({
              withCredentials: true,
              lang: 'zh_CN',
            });
            // @todo 换 token 的时候出现了错误需要处理
            try {
              res = await getToken(data.code, user.iv, user.encryptedData);
            } catch (err) {
              throw err;
            }
          }
          authorization.token = res.data.access_token;
          authPromise = null;
          // 顺便获取一次
          store.dispatch('user/USER_GET_PROFILE')
          resolve();
        } catch (e) {
          const set = await WXP.getSetting();
          const router = Vue.prototype.$router;
          if (!set.authSetting['scope.userInfo']) {
            router.push('/pages/user/login');
            try {
              await new Promise((res, rej) => {
                Vue.bus.on('ON_USER_LOGIN', (action, data) => {
                  if (action === true) {
                    res(data);
                  } else {
                    rej();
                  }
                });
              });
            } catch (err) {
              // 暂时先什么都不做 用户必须授权
              // authPromise = null;
              // reject(err);
              // throw err;
            }
            Vue.bus.off('ON_USER_LOGIN')
          }
        }
      }
    });


    2018-09-06
    有用 2
    回复
  • 壺天
    壺天
    2019-01-14

    我感觉目前getuserinfo的button存在bug,并不是session的问题

    说下我的流程:


    1、页面onload,里wx.checksession,fail了就wx.login,第一次打印log输出未过期

    2、页面加载后立刻点击getUserInfo的button

    3、getUserInfo的回调中,首先wx.login(这个实际上不需要,这里只是为了复现bug),获取code

    4、发送code和button回调中的rawData和签名A到服务端

    5、服务端验签失败,输入log中,sessionkey为A


    6、立刻第二次点击(第二步),此时checksession依然输出未过期

    7、发送新code和统一的rawData还有签名B到服务端,注意这里的签名B肯定跟刚才的签名A不一样

    8、服务端验签成功,log中显示,sessionkey依然为A


    第二次尝试,中间增加wx.getUserInfo:


    1、页面onload,里wx.checksession,fail了就wx.login,第一次打印log输出未过期

    2、页面加载后立刻点击getUserInfo的button

    3、getUserInfo的回调中,首先wx.login(这个实际上不需要,这里只是为了复现bug),获取code

    4、调用wx.getUserInfo,打印log,输出签名 W1

    5、发送code和button回调中的rawData和签名A到服务端

    6、服务端验签失败,输入log中,sessionkey为A


    6、立刻第二次点击(第二步),此时checksession依然输出未过期

    7、发送新code和统一的rawData还有签名B到服务端,注意这里的签名B肯定跟刚才的签名A不一样

    8、调用wx.getUserInfo,打印log,注意输出签名仍为 W1

    9、服务端验签成功,log中显示,sessionkey依然为A


    结论是:

    1、在一个生命周期内,多次调用wx.login并不会改变code换回来的sessionkey

    2、在wx.login中,button和wx.getUserInfo返回的签名不同,但多次点击,每一次的button和button是一样的,wx.getUserInfo和wx.getUserInfo也是一样的,但是一小段时间后,就都会换新的签名了

    3、但是2中有个问题,如果页面刷新,过一会点击,这时候button返回的签名是无效的,wx.getUserInfo是有效的!再次点击,button的签名换了(这时候是有效的),wx.getUserInfo的签名没变,这时候也是有效的

    4、最简单的处理办法是button回调里面再调wx.getUserInfo一次,用wx.getUserInfo的信息去请求服务端,此方式比较繁琐但没有任何问题


    具体还要看官方人员排查,开发者也要维护要checksession和wx.login,不要频繁login

    2019-01-14
    有用
    回复
  • Kevin张吕梁
    Kevin张吕梁
    2018-09-06

    学习了。

    2018-09-06
    有用
    回复
  • 2018-09-05

    为什么我一个米大师应用名称同步的问题,上礼拜五到现在都没人可以帮我们解决呢? 在这里却看到了3个不同名字的官方人员。。真心求助

    小游戏改名后,米大师中应用名称不能同步,求协助

    问题模块框架类型问题类型操作时间AppID
    管理后台小游戏Bug昨天 00:00wx0c9ea6721d1da882

    - 当前 Bug 的表现(可附上截图)

    小程序改名后,米大师中应用名称不能同步,求协助,着急

    appid:wx0c9ea6721d1da882


    2018-09-05
    有用
    回复
  • 幽灵柯南
    幽灵柯南
    2018-09-05

    我提的问题,也经常石沉大海,那个开发者工具在笔记本上不能使用的问题,几年了 还没解决

    2018-09-05
    有用
    回复
  • 以梦为马i
    以梦为马i
    2018-09-05

    你这条帖子是我见过评论最长回复最长的一条帖子。

    2018-09-05
    有用
    回复 2
    • zluo
      zluo
      2018-09-05

      哈哈,也是没办法,一开始提的有点情绪,但作为开发者,确实是想切切实实的能解决问题,不管是哪一方的问题,开发者千千万万,总归会有各种重复的或者甚至在他们官方眼里是弱智的问题,但从开发者角度讲,还是希望能得到有效的帮助,而不是互嘲,总之我相信,不管谁写的代码,总有错的。当然你提到的,很多人提到的为什么官方基本都没回复的,我想下面的截图多多少少能解释下这个问题:

      这是官方人员的解答。

      很可能我们的问题已经被定义在了8%中。

      2018-09-05
      回复
    • 枫儿
      枫儿
      2018-09-05回复zluo

      但是微信方面开发,官方支持是出了名的难找!!大概比使用开源的东西提issues还难,效率还低

      2018-09-05
      回复
  • zluo
    zluo
    2018-09-04

    谢谢您这么及时的回答问题,我姑且认为是回答。首先这个发帖人我完全不认识,所以说明这个问题似乎不需要定向来排查吧?很多不一样的开发者都遇到了相同的问题,当然不说100%排除是我们这些人犯了同样的错误,我希望也是我们犯了错,因为毕竟你们比我们牛,几乎不会出错,而且我们错了更容易改,对吧?另外你们在提供这些相对敏感的信息时候,有保密的文本吗?直接贴在公开的论坛里让别人看到appid和openid?虽然这不是secret,但总感觉有点怪怪得啊,一般其他平台,提供这些敏感信息,都是有保密的字段可以填写的,只有你们官方可以看到,不是吗?另外既然您说了要appid,那我这正好有2个,我想说的是这2个都同时有这个问题。另外我比较弱鸡,还是请麻烦解答下,直接这么公开的提供appid和openid没啥安全隐患吧?再次感谢您专业的帮助。

    wx345ace1cb60e9fdf

    wxaa0ddc4b1f7e3156

    o_WKt4rN6LTl6aQR2Fb9ax_35zZU

    2018-09-04
    有用
    回复 15
    • 洋葱头
      洋葱头
      2018-09-04

      刷新下页面,我修改了回答。

      2018-09-04
      回复
    • zluo
      zluo
      2018-09-04

      谢谢,抱怨提问我是承认的,因为个人感觉,你们既然已经有了这么好的经验,也看到了别人的问题,为什么不直接回答下呢?而是直接忽略?或者你们针对这样“线上线下我们定位过非常多次”的问题,有什么相关的排查文档可以查看?我相信,专业我们这些无知的开发者也不会让你们感到厌烦不是吗?我还是想说下自己的看法,虽然可能还是错的,也不怕您的耻笑。

      1用错code,请高手明示,何为用错了code?code是直接通过wx.login成功返回的。

      2重复用 code,我上面的提问中已经截图了,2次code不同

      3解密算法错,同一解密算法,要错就没对的时候吧?

      4用错 session key,同样何为用错seesion key?用的是这个地址

      https://api.weixin.qq.com/sns/jscode2session

      加appid secret加上code获取的。


      提问要诚恳,如果有让您觉得不开心的,我表示抱歉,我也完全能接受是我自身的问题导致的,但是还是希望您能帮忙解答下,谢谢。

      2018-09-04
      回复
    • zluo
      zluo
      2018-09-04

      抱歉,补上解密部分,就2句话,

      const crypto = require('crypto');

      const sha1 = crypto.createHash('sha1').update(fullUserInfo.rawData + sessionData.session_key).digest('hex');


      2018-09-04
      回复
    • 2018-09-05回复洋葱头

      您好,我有一个比较急的疑问

      -------------


      之前通过 `wx.getUserInfo()` 获取用户信息时,它有个前提条件要求先 `wx.login()` 而且未过期。现在换成通过按钮触发的形式后,是在哪一个阶段获取授权code呢?是在点击按钮之前就要获取到code?还是在bindgetuserinfo的回调函数内部获取code?


      bindgetuserinfo的回调函数内部获取的code,会不会是无效的?(我在体验版是有效的),以为部分JS源码(mpvue):

      <script>
       
        export default {
       
          methods: {
       
            async onGetUserInfo (res) {  // bindgetuserinfo的回调函数
       
              const that = this
       
              const { userInfo, errMsg, encryptedData, iv } = res.target
       
              if (errMsg === 'getUserInfo:ok') {
       
                wx.login({
       
                  success (loginRet) {
       
                    if (loginRet['code']) {
       
                      that.loginAfter({
       
                        userInfo,
       
                        encryptedData,
       
                        iv,
       
                        code: loginRet['code']
       
                      })
       
                    }
       
                  },
       
                  complete (loginRet) {
       
                    if (!loginRet['code']) {
       
                      wx.showModal({ title: '授权失败' })
       
                    }
       
                  }
       
                })
       
              }
       
            },
       
            loginAfter ({ userInfo, encryptedData, iv, code }) {
       
              // do something
       
            }
       
          }
       
        }
       
      </script>


      2018-09-05
      回复
    • zluo
      zluo
      2018-09-05回复

      我们问的应该是同一个问题,在回调里再获取code,此时的UserInfo很可能就已经是无效的了。但是不知道能否直接在回调函数里,直接再wx.getUserInfo一次,如果可以,这个问题也能暂时完美的解决掉,马上测试下,呵呵。

      2018-09-05
      回复
    查看更多(10)
登录 后发表内容