我必定是熬夜熬傻了,小程序后台获取用户信息竟然发生了这件事

微信小程序获取用户手机号后端处理

前面我不是整理了一套微信小程序开发的api啊,原由是由于一个朋友接了一个私活,咱们一块儿帮忙开发,而后再这个小程序里面有一个功能,就是经过微信小程序获取用户手机号后端处理,来看一下这个功能在微信官方是怎么定义的前端

注:程序员在本身的技术能力提高以后,闲来无事接一些小的开发任务,不沉重,可是久而久之,其实真的赚的很多,去吃顿好吃的旅个游不香吗?java

好了,言归正传程序员

官网流程概述

微信官方网页对于微信小程序获取用户手机号的处理描述以下
image.pngredis

这页主要是对前端作法的描述,主要描述了前端应该提早经过wx.login登录,或者进行登陆态检查,以此避免刷新登陆态的操做,避免出现服务端存的sessionKey不是最新的sessionKey从而出现敏感数据解密失败的问题。前端经过button触发bindgetphonenumber事件,拿到加密数据传给后端,后端经过解密算法解密。算法

后端逻辑


微信会对这些开放数据作签名和加密处理。开发者后台拿到开放数据后能够对数据进行校验签名和解密,来保证数据不被篡改。因此
一、前端经过调用接口(如 wx.getUserInfo)获取数据时,接口会同时返回 rawData、signature,其中 signature = sha1( rawData + session_key )
二、开发者将 signature、rawData 发送到开发者服务器进行校验。服务器利用用户对应的 session_key 使用相同的算法计算出签名 signature2 ,比对 signature 与 signature2 便可校验数据的完整性。apache

数据校验

后端拿到rawData后经过SHA1()算法对后端存储的sessionKey进行SHA1(rowData,sessionKey)加密,若是获得的signature与前端传来的signature一致,则校验成功。(获取用户手机号时数据校验不是必须的,因此此处只叙述逻辑并未实现)小程序

数据解密

官方文档对数据解密的解释是这样的:
接口若是涉及敏感数据(如wx.getUserInfo当中的 openId 和 unionId),接口的明文内容将不包含这些敏感数据。开发者如须要获取敏感数据,须要对接口返回的加密数据(encryptedData) 进行对称解密。解密算法以下:
对称解密使用的算法为 AES-128-CBC,数据采用PKCS#7填充。
对称解密的目标密文为 Base64_Decode(encryptedData)。
对称解密秘钥 aeskey = Base64_Decode(session_key), aeskey 是16字节。
对称解密算法初始向量 为Base64_Decode(iv),其中iv由数据接口返回。后端

因此笔者进行数据解密时采用了网络上搜的解密工具使用
我获得简洁的解密工具以下:微信小程序

`public class AesUtil {
    public static String wxDecrypt (String encrypted, String sessionKey, String iv)throws Exception {
        byte[] encrypData = Base64.decodeBase64(encrypted);
        byte[] ivData = Base64.decodeBase64(iv);
        byte[] sKey = Base64.decodeBase64(sessionKey);
        String decrypt = decrypt(sKey,ivData,encrypData);
        return decrypt;
    }
    public static String decrypt(byte[] key, byte[] iv, byte[] encData) throws Exception {
        AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        //解析解密后的字符串
        return new String(cipher.doFinal(encData),"UTF-8");
    }
}
`

*   
*

按照常理来讲(以小编神奇的99%运气+1%实力)对比官网进行开发,一把不会出现问题,可是,没想到,阴沟里翻船了(最后得知bug缘由的我被无情嘲讽)api

代码在测试阶段出现了问题,抛出“BadPaddingException: Given final block not properly padded. Such issues can”开头的异常

通过问题排查,找出问题

一、微信小程序开发者工具获取的sessionKey有效期为5分钟,过期以后sessionKey就会解不出来
二、完善解密算法,增长处理异常的try–catch块,更换getInstance参数

解决上述两个问题以后,优化了工具,拿到用户手机号并成功返回。
返回格式与微信官方网站上一致。
格式以下

`{
    "phoneNumber": "13580006666",
    "purePhoneNumber": "13580006666",
    "countryCode": "86",
    "watermark":
    {
        "appid":"APPID",
        "timestamp": TIMESTAMP
    }
}
`

*   
*

后端实现

此处贴上实现代码(后端接收由SpringBoot实现):

Controller层实现:

`@PostMapping("/member/phone")
    @ApiOperation(value = "获取用户手机号", notes = "说明:")
    public PlatformResult getPhoneNumber(
            @ApiParam(name = "encryptedData", value = "加密数据")
            @RequestParam("encryptedData") String encryptedData,
            @ApiParam(name = "iv", value = "加密算法的初始向量")
            @RequestParam("iv") String iv) throws Exception {
        //TODO:从redis里获取sessionKey
        String sessionKey = (String) redisUtil.get(sessionKeyPre + jwtTokenInfoUtil.getMemberIdByToken());
        if (null != sessionKey && !sessionKey.isEmpty()) {
            String s = AesUtil.wxDecrypt(encryptedData, sessionKey, iv);
            JSONObject object = JSONObject.parseObject(s);
            Object number = object.get("phoneNumber");
            if (null != number) {
                MeMemberInfo memberInfo = new MeMemberInfo();
                memberInfo.setId(jwtTokenInfoUtil.getMemberIdByToken());
                memberInfo.setPhone(number.toString());//更新绑定手机号
                memberInfo.setPhoneStatus(ComConstants.PHONE_STATUS_0);//置为绑定手机状态
                readMeMemberInfoService.updateById(memberInfo);
                return PlatformResult.success(number.toString());
            }
        }
            return PlatformResult.failure(ResultCode.RESULE_DATA_NONE);
    }`

*   
*

解密工具AesUtil实现:

package com.andrea.platform.util;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
/**
 * @program: aes-service
 * @description: 加密信息解密
 * @author: Andrea_nul
 * @create: 2020-9-28 23:57
 **/
public class AesUtil {
    public static String wxDecrypt (String encrypted, String sessionKey, String iv)throws Exception {
        byte[] encrypData = Base64.decodeBase64(encrypted);
        byte[] ivData = Base64.decodeBase64(iv);
        byte[] sKey = Base64.decodeBase64(sessionKey);
        String decrypt = decrypt(sKey,ivData,encrypData);
        return decrypt;
    }
    public static String decrypt(byte[] key, byte[] iv, byte[] encData) throws Exception {
//        AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
//        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
//        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
//        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        //解析解密后的字符串
        String resultString = null;
        AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
            resultString = new String(cipher.doFinal(encData), "UTF-8");
        } catch (Exception e) {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
            resultString = new String(cipher.doFinal(encData), "UTF-8");
        }
        return resultString;
    }
}

好了,完美解决,哎,真的是,可是我以为不是我实力的问题,确定是微信官网没有写好(就这样,不容反驳,嘿嘿嘿)

好了,最后仍是建议你们,没啥事的时候能够多去看一下官网的内容,或者本身debug一下源码,总能发现一些在平时开发的时候遇不到的问题,解决的问题的过程就是成长

我是小Q,热爱技术,也爱生活,坚持分享输出,让本身和读者都有收获!关注我来跟我一块儿变强吧。公众号:Java架构师联盟,每日更新技术好文

相关文章
相关标签/搜索