微信支付宝解析用户绑定的敏感数据(手机号码)

在我们公司的项目中,想要让用户在微信小程序和支付宝小程序中填写一些必须的信息资料,像用户的手机号码这些,但是对于一些用户可能会嫌麻烦,所以我们就想做到直接获取到微信绑定的手机号码和支付宝绑定的手机号码,只需要用户授权就可以了。
由于像微信、支付宝绑定手机号码为用户的敏感信息,前端只能够获取到用户信息的加密数据,需要Java开发者后台进行解密,下面是根据微信和支付宝写的解析接口
微信:

导包如下(导入的包中包含了微信和支付宝接口的,还有可能会有一些无用包,因为我是直接把我写的接口拿出来了,这个控制器里面还有其他接口,就不粘了):

import java.security.AlgorithmParameters;
import java.security.Security;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.parser.Feature;
import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipayEncrypt;
import com.alipay.api.internal.util.AlipaySignature;
import com.google.gson.Gson;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import com.tp.dto.UserAliSecretInfoDTO;
import com.tp.dto.UserWxSecretInfoDTO;
import com.tp.search.UserWxSecretInfoSearch;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.web.bind.annotation.*;

import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipaySystemOauthTokenRequest;
import com.alipay.api.response.AlipaySystemOauthTokenResponse;
import com.squareup.okhttp.OkHttpClient;
import com.tp.ajax.ApiResult;
import com.tp.common.Constant.Ali;
import com.tp.common.Constant.Weixin;
import com.tp.enums.ExceptionCode;
import com.tp.exception.BaseException;
import com.tp.service.WxRemoteApiService;
import com.tp.utils.LogUtils;

import retrofit.Call;
import retrofit.GsonConverterFactory;
import retrofit.Retrofit;
import retrofit.RxJavaCallAdapterFactory;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
@ResponseBody
    @PostMapping("/mini/wx/phone")
    public ApiResult getMiniWxPhone(@RequestBody UserWxSecretInfoSearch userSecretInfoSearch) {
        if (null == userSecretInfoSearch.getEncryptedData() || null == userSecretInfoSearch.getSessionKey() || null == userSecretInfoSearch.getIv()) {
            throw new BaseException(ExceptionCode.PARAMETER_MISSING);
        }
        // 被加密的数据
        byte[] dataByte = Base64.decode(userSecretInfoSearch.getEncryptedData());
        // 加密秘钥
        byte[] keyByte = Base64.decode(userSecretInfoSearch.getSessionKey());
        // 偏移量
        byte[] ivByte = Base64.decode(userSecretInfoSearch.getIv());

        try {
            // 如果密钥不足16位,那么就补足.  这个if 中的内容很重要
            int base = 16;
            if (keyByte.length % base != 0) {
                int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
                byte[] temp = new byte[groups * base];
                Arrays.fill(temp, (byte) 0);
                System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
                keyByte = temp;
            }
            // 初始化
            Security.addProvider(new BouncyCastleProvider());
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding","BC");
            SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
            AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
            parameters.init(new IvParameterSpec(ivByte));
            cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
            byte[] resultByte = cipher.doFinal(dataByte);
            if (null != resultByte && resultByte.length > 0) {
                String result = new String(resultByte, "UTF-8");
                UserWxSecretInfoDTO userSecretInfoDTO = new Gson().fromJson(result, UserWxSecretInfoDTO.class);
                return ApiResult.ok(userSecretInfoDTO);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ApiResult.error(ExceptionCode.PARAMETER_WRONG,"反解密码失败");
    }

其中UserWxSecretInfoSearch是一个我自己定义的接收类,我也贴出来:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

//BaseSearch类见我的中的工具类,另外:本篇文章中的像ApiResult和ExceptionCode都是自己定义的一些工具类和异常枚举类,里面是一些异常返回的code和一些具体的异常错误信息
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
public final class UserWxSecretInfoSearch extends BaseSearch {

    //微信获取敏感信息手机号码
    private String encryptedData;

    private String sessionKey;

    private String iv;

}

UserWxSecretInfoDTO也是我自己定义的一个接收类,如下:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserWxSecretInfoDTO {
    String phoneNumber;

    String purePhoneNumber;

    String countryCode;
}

微信我是直接参考的下面的链接:
https://www.cnblogs.com/handsomejunhong/p/8670367.html
同时参考了微信的官方文档:
https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html
https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html

支付宝获取用户的敏感数据

@ResponseBody
    @PostMapping("/mini/ali/phone")
    public ApiResult getMiniAliPhone(@RequestBody String encryptContent) {
        //System.out.println("我调用了支付宝小程序的敏感信息的接口");
        try {

            //1. 获取验签和解密所需要的参数
            Map openapiResult = JSON.parseObject(encryptContent,
                    new TypeReference>() {
                    }, Feature.OrderedField);
            String signType = StringUtils.defaultIfBlank(openapiResult.get("signType"), "RSA2");
            String charset = StringUtils.defaultIfBlank(openapiResult.get("charset"), "UTF-8");
            String encryptType = StringUtils.defaultIfBlank(openapiResult.get("encryptType"), "AES");
            String sign = openapiResult.get("sign");
            String content = openapiResult.get("response");

            boolean isDataEncrypted = !content.startsWith("{");
            boolean signCheckPass = false;

            //2. 验签
            String signContent = content;
            String signVeriKey = Ali.MINI_APP_PUBLIC_KEY;
            String decryptKey = Ali.AES;
            //如果是加密的报文则需要在密文的前后添加双引号
            if (isDataEncrypted) {
                signContent = "\"" + signContent + "\"";
            }
            try {
                signCheckPass = AlipaySignature.rsaCheck(signContent, sign, signVeriKey, charset, signType);
            } catch (AlipayApiException e) {
                //验签异常, 日志
                return ApiResult.error(ExceptionCode.ALI_SIGN_FAILURE, "验签异常");
            }
            if(!signCheckPass) {
                //验签不通过(异常或者报文被篡改),终止流程(不需要做解密)
                return ApiResult.error(ExceptionCode.ALI_SIGN_FAILURE, "验签不通过,异常或者报文被修改");
            }

            //3. 解密
            String plainData = null;
            if (isDataEncrypted) {
                try {
                    plainData = AlipayEncrypt.decryptContent(content, encryptType, decryptKey, charset);
                } catch (AlipayApiException e) {
                    //解密异常, 记录日志
                    return ApiResult.error(ExceptionCode.ALI_DECRIPT_FAILURE);
                }
            } else {
                plainData = content;
            }
            UserAliSecretInfoDTO userAliSecretInfoDTO = new Gson().fromJson(plainData, UserAliSecretInfoDTO.class);
            //System.out.println("获取到的解密信息为:" + new Gson().toJson(userAliSecretInfoDTO));
            return ApiResult.ok(userAliSecretInfoDTO);
        } catch (Exception e) {
            return ApiResult.error(ExceptionCode.ALI_DECRIPT_FAILURE, "验签解密异常");
        }
    }

其中UserAliSecretInfoDTO是我自己定义的一个接收类,如下:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserAliSecretInfoDTO {
    String code;

    String msg;

    String mobile;
}

这是我直接看官方文档然后百度借鉴了其他大神的获取敏感手机号的方法。
不过我在写的过程中遇到了几个坑:
1.首先是解析前端传过来的加密信息的时候,采用fastjson处理,但是要注意参数解析的顺序问题,即Feature.OrderedField,一开始一直没有OrderedField这个值,最后查看了是由于fastjson版本太老了,好像是1.2.0的,需要更换比较新的fastjson包,我换成了1.2.54版本的
2.传过来的也就是后台接收的参数encryptContent是包含了response、sign、sign_type、encrypt_type以及charset的字符串,Map openapiResult = JSON.parseObject(encryptContent,
new TypeReference>() {
}, Feature.OrderedField);里面的encryptContent就是传过来的一串东西,我一开始接收参数直接将它解析出来传进去的是解析出来的response,自然这样是解析不出来的。
3.支付宝小程序解析用户信息比较麻烦的,必须提交阿里那边审核说明为什么要获取用户敏感信息,审核通过后才能够获取,否则会报40006,即{"code":"40006","msg":"Insufficient Permissions","subCode":"isv.insufficient-isv-permissions","subMsg":"ISV权限不足,建议在开发者中心检查对应功能是否已经添加"}

支付宝小程序获取敏感数据我参考了以下网页以及官方文档:

https://docs.alipay.com/mini/introduce/aes
https://www.cnblogs.com/hujunzheng/p/10184418.html

你可能感兴趣的:(微信支付宝解析用户绑定的敏感数据(手机号码))