广告位 · 页头(后台-模块管理-新建模块后替换此区域)

微信公众号网页授权后如何解析getPhoneNumber返回的手机号数据?

在微信公众号网页里获取用户手机号,很多开发者容易把网页授权和getPhoneNumber组件混为一谈。网页授权只负责拿到当前用户的openid及基础资料,手机号属于更高级别的隐私数据,必须通过微信的手机号快速验证组件单独获取。前端调用wx.invoke获取到一个临时code后,后端再用code去微信服务器换手机号,最终返回的JSON里phone_info才是我们需要解析的核心内容。整个链路可以拆成三步:前端拿到code、后端调用接口换取手机号、解析phone_info并做安全校验。

微信公众号网页授权后如何解析getPhoneNumber返回的手机号数据?

getPhoneNumber接口到底返回了什么

前端通过JSSDK调用getPhoneNumber时,回调结果里并没有手机号本身,只有一个短时效的code。这个code的有效期通常只有5分钟,并且只能使用一次。后端拿到code后,需要调用微信的服务器接口,用code换取真正的手机号数据。这个接口的返回结构非常固定,只要调用成功,errcode为0,响应体里就会包含一个phone_info对象。

一个典型的成功返回JSON如下所示:

{
  "errcode": 0,
  "errmsg": "ok",
  "phone_info": {
    "phoneNumber": "13800138000",
    "purePhoneNumber": "13800138000",
    "countryCode": "86",
    "watermark": {
      "timestamp": 1633333333,
      "appid": "wx1234567890abcdef"
    }
  }
}

这里需要特别说明phone_info里几个字段的含义。phoneNumber是带国家码的完整手机号,但在国内环境下它通常和purePhoneNumber长得一样;purePhoneNumber是不带国家码的纯手机号,适合直接用于登录或绑定业务;countryCode是国家区号,国内用户返回86;watermark则用来标识这份数据是由哪个appid在什么时间生成的。很多开发者拿到phoneNumber就直接入库,却忽略了后面这个watermark对象,其实后面的安全校验恰恰要靠它来完成。

后端解析getPhoneNumber返回数据的完整流程

解析手机号之前,后端要先准备好有效的access_token。这里强调一下,公众号H5场景使用的是认证服务号的access_token,不是网页授权返回的access_token。先用appid和secret调微信的token接口,拿到access_token后再拼接换取手机号的业务接口地址。有些开发者因为两个token名字一样就直接复用网页授权接口返回的token,结果会发现调用报错,问题就出在这里。

下面给出一段PHP的完整解析示例,代码里同时包含了获取access_token、调用getPhoneNumber业务接口、解析phone_info以及校验appid的逻辑。

<?php
// 获取前端传来的code
$code = $_POST['code'];
$appid = '你的公众号appid';
$secret = '你的公众号secret';
// 获取服务号access_token
$tokenUrl = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=' . $appid . '&secret=' . $secret;
$tokenRes = file_get_contents($tokenUrl);
$tokenData = json_decode($tokenRes, true);
$accessToken = $tokenData['access_token'];
// 调用手机号快速验证组件接口
$url = 'https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=' . $accessToken;
$postData = json_encode(['code' => $code]);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
if ($result['errcode'] === 0) {
    $phoneInfo = $result['phone_info'];
    $purePhoneNumber = $phoneInfo['purePhoneNumber'];
    $countryCode = $phoneInfo['countryCode'];
    $watermark = $phoneInfo['watermark'];
    // 校验appid是否与当前公众号一致
    if ($watermark['appid'] !== $appid) {
        exit('手机号数据来源异常');
    }
    echo '用户手机号:' . $purePhoneNumber;
} else {
    echo '错误码:' . $result['errcode'] . ',错误信息:' . $result['errmsg'];
}

解析出手机号之后,不能忽略安全校验。watermark里的appid必须与当前公众号的appid完全一致,否则这份手机号数据可能来自其他应用,属于异常情况。同时可以检查watermark里的timestamp,如果与当前服务器时间相差超过几分钟,很可能是一次重放请求,应当拒绝处理。手机号本身属于个人敏感信息,落库时建议使用AES等算法加密存储,前端展示时做掩码处理,比如只显示前三位和后四位。

在实际联调中还可能遇到一些非零的errcode。errcode为40029通常表示code已经过期或者已经被使用过;40013说明access_token无效或appid不匹配;47001是请求参数格式错误,检查一下postData是不是标准JSON;48001表示当前公众号没有开通手机号快速验证组件的权限。看到这些错误码时,先根据官方文档逐一排查,能少走很多弯路。

公众号网页授权与小程序获取手机号的差异

同样是getPhoneNumber,公众号H5和小程序的前端调用方式截然不同。小程序端通过button组件的open-type属性直接触发,用户点击后微信客户端会弹出授权面板,不需要额外的JSSDK配置。公众号H5则必须在微信内置浏览器里先完成wx.config签名,再通过wx.invoke触发getPhoneNumber。如果页面被用户复制到外部浏览器打开,或者JSSDK签名参数有误,组件会直接不可用。

另一个明显的差异是access_token的来源。小程序获取手机号用的是小程序自己的appid和secret生成的access_token,而公众号H5则必须使用已认证服务号的access_token。两者后端调用换取手机号的接口地址虽然路径里都带有wxa字样,但这是微信统一业务接口,并非小程序专属。开发者不要把公众号的appid配到小程序的token接口上,否则拿到的token再调getPhoneNumber接口会直接报40013。

从数据返回结构上看,两者基本没有区别,都是errcode加上phone_info对象。因此同一套后端解析逻辑可以同时服务于公众号H5和小程序两个入口。只需要在配置层做好appid、secret以及对应token的隔离,业务代码几乎可以复用。

前端调用与后端解析的衔接建议

前端调用getPhoneNumber的典型写法如下,回调成功后立刻把code通过Ajax或fetch传给后端,不要让code在前端停留过久,也不要尝试在前端解析任何结果。

wx.invoke('getPhoneNumber', { sign: sign }, function (res) {
  if (res.err_msg === 'getPhoneNumber:ok') {
    fetch('/api/phone', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ code: res.code })
    }).then(function (response) {
      return response.json();
    }).then(function (data) {
      console.log(data.phoneNumber);
    });
  }
});

这里面的sign参数是前端调用手机号快速验证组件时必须携带的签名,它需要通过公众号的jsapi_ticket计算生成。签名错误会导致wx.invoke直接失败,err_msg里会提示invalid signature。计算签名时要注意URL不能包含hash部分,并且要用页面完整的当前地址,包括参数,不然很容易出现签名校验不通过的问题。

后端接口设计上,建议把手机号获取接口做成一次性请求,前端传code,后端成功获取手机号后立刻返回一个业务token,后续用户登录态基于这个业务token维护。不要每次操作都重新走一遍手机号获取流程,也不必把手机号明文反复传到前端。用户的隐私数据应当仅在服务端流动,前端只拿一个标识即可。

还有一点容易忽略,就是用户的授权状态。getPhoneNumber组件在每次调用时都会弹出微信授权框,如果用户拒绝授权,回调里err_msg会返回取消信息。此时后端不会收到code,也不需要额外处理。真正要处理的是用户已经授权过一次、code已经被使用后的重复提交情况,这种时候返回40029的概率很高,后端应当给出友好的提示,让用户重新发起授权。

综合来看,解析getPhoneNumber返回的手机号并不复杂,难的是把前后端衔接、安全校验和错误处理都考虑完整。只要把code的一次性机制理解清楚,把phone_info里的字段逐个校验到位,公众号网页获取手机号这件事就能做得既稳又安全。

广告位 · 文章底部(后台-模块管理替换此区域)

猜你喜欢

YOU LIKE