从微信用户加密数据encryptedData获取用户信息userInfo和unionId

 

Java后端实现小程序的微信登录时解析加密数据获取unionid

微信登录具体参考:https://blog.csdn.net/weixin_42346767/article/details/102508736

流程简介:

1. 准备解密必须的jar包或导入相关依赖

2. 创建解密的API或工具类

3. 调用解析

 

详细介绍及异常配置:

1. 准备解密必须的jar包或导入相关依赖

// maven  的pom依赖
 

   org.bouncycastle
   bcprov-jdk16
   1.46

2. 创建解密的API或工具类

package com.magic.nzoth.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.AlgorithmParameters;
import java.security.Security;

/**
 * @program: 
 * @description: 微信用户加密信息解密工具
 * @author: mingqi
 * @create: 2019-10-11 15:53
 **/
@Slf4j
public class AesCbcUtil {

//    static {
//        // BouncyCastle是一个开源的加解密解决方案
//        Security.addProvider(new BouncyCastleProvider());
//    }

    // 固定编码
    private static final String encodingFormat = "UTF-8";

    /**
     * 对外提供服务
     * @return
     */
    public static String decodeUserInfo(String data, String key, String iv){
        return decodeBase64(data, key, iv);
    }

    /**
     * AES解密
     *
     * @param data // 密文,被加密的数据
     * @param key  // 秘钥
     * @param iv   // 偏移量
     *             //     * @param encodingFormat 	// 解密后的结果需要进行的编码
     * @return
     */
    private static String decodeBase64(String data, String key, String iv) {
        // 被加密的数据
        byte[] dataByte = Base64.decodeBase64(data);
        // 加密秘钥
        byte[] keyByte = Base64.decodeBase64(key);
        // 偏移量
        byte[] ivByte = Base64.decodeBase64(iv);

        // BouncyCastle是一个开源的加解密解决方案
        Security.addProvider(new BouncyCastleProvider());

        try {
//            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
//            Cipher cipher = Cipher.getInstance("BC");
            Cipher cipher = Cipher.getInstance("AES/CBC/NOPadding");

            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, encodingFormat);
                return result;
            }
            return null;
        } catch (Exception e) {
            log.info("解析失败:{}", e);
        }
        return null;
    }

}

3. 调用解析

/**
     * 解密微信返回的用户加密数据
     *
     * @param sessionKey
     * @param iv
     * @param encryptedData
     */
    private JSONObject decodeUserData(String sessionKey, String iv, String encryptedData) {

        JSONObject decodeData;
        try {
            log.info("解密数据:{},初始向量:{}, sessino_key:{}", encryptedData, iv, sessionKey);
            String userdata = AesCbcUtil.decodeUserInfo(encryptedData, sessionKey, iv);
            decodeData = JSON.parseObject(userdata);
            log.info("解密后数据:{}", decodeData);
            if (!ObjectUtils.isEmpty(decodeData)) {
                return decodeData;
            }
        } catch (Exception e) {
            log.info("微信解密失败:{}", e);
        }
        return null;
    }

4. 解析后的数据应用

try {
    // code2Session 接口未返回unionid字段,表明用户未关注公众号,同时需要特殊处理获取该字段
    JSONObject decodeData = decodeUserData(session_key, iv, encryptedData);
    if (!ObjectUtils.isEmpty(decodeData)) {
        userInfo = decodeUserData(session_key, iv, encryptedData);
        unionId = userInfo.getString("unionId");
    } else {
        unionId = "微信用户数据解析失败";
    }
} catch (Exception e) {
    log.info("解析获取用户数据报错:{}", e.getClass());
}

异常提示:

情形一:

最开始解析工具类中Security.addProvider(new BouncyCastleProvider());是使用静态代码块初始加载的

// 报错:
javax.crypto.BadPaddingException: pad block corrupted
    at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher$BufferedGenericBlockCipher.doFinal(Unknown Source)
    at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(Unknown Source)
    at javax.crypto.Cipher.doFinal(Cipher.java:2164)
    at com.magic.nzoth.util.AesCbcUtil.decrypt(AesCbcUtil.java:63)


// 解析工具类配置:
    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");

    网上给出的结局方案是AES 可以正常加解密,但是多用户并发操作时,有时会出现以上错误!

    工具类中 将 cipher 定义为静态常量了,cipher 不是单例模式,改为调用时生成加载的方式

参考自:https://blog.csdn.net/yszd2017/article/details/78422608

情形二:

// 解析工具类配置:
    // BouncyCastle是一个开源的加解密解决方案
    // 改行代码放到具体的方法中进行调用,而非静态代码块
    Security.addProvider(new BouncyCastleProvider());

    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");

报错:
com.alibaba.fastjson.JSONException: syntax error, pos 1, json : �ޜ�nX���V��f�g"_��{3[��ê�l���\��͗�j��T��+Q�4�U�����p�+�-��#�ƆIA�zf9�}�"�W���
��:���t".u˘B�`'I�2x6K�,8�Y�o��������M���$v��}�2�

解密后的数据存在乱码情况,想起了最开始捣鼓的时候依稀记得有说需要使用BC解密方式的,

情形三:

// 解析工具类配置:
    // BouncyCastle是一个开源的加解密解决方案
    Security.addProvider(new BouncyCastleProvider());

    Cipher cipher = Cipher.getInstance("BC");

// 报错:
java.security.NoSuchAlgorithmException: Cannot find any provider supporting BC
    at javax.crypto.Cipher.getInstance(Cipher.java:539)
    at com.magic.nzoth.util.AesCbcUtil.decrypt(AesCbcUtil.java:53)

haha ~ ,尴尬了,找不到BC解密方式的 支持

情形四:

// 解析工具类配置:
    // BouncyCastle是一个开源的加解密解决方案
    Security.addProvider(new BouncyCastleProvider());

    Cipher cipher = Cipher.getInstance("AES/CBC/NOPadding");

// 最开始的AES/CBC/PKCS7Padding 配置到目前的 AES/CBC/NOPadding
// 可以解决报错,但是偶尔会乱码

// 成功情况
解密后数据:{"country":"China","unionId":"unionId","watermark":{"appid":"appid","timestamp":1570783572},"gender":1,"province":"Beijing","city":"Haidian","avatarUrl":"avatarUrl","openId":"openId","nickName":"有点过分,但是","language":"zh_CN"}

// 异常情况
���a�  ?��E�@�D�pXH���)��p�PK`s����x+%�Z��M��Dlgٹq��n4_����/��0�j����d@MN���NA�������O���s��^���U�����\�w�
    at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1436)
    at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1322)
    at com.alibaba.fastjson.JSON.parse(JSON.java:152)
    at com.alibaba.fastjson.JSON.parse(JSON.java:162)
    at com.alibaba.fastjson.JSON.parse(JSON.java:131)
    at com.alibaba.fastjson.JSON.parseObject(JSON.java:223)


// 目前的现状是一次成功,一次失败

在网上看到一篇说是因为前端在调用wx.login  和 wx.getUserInfo 的接口顺序不一致会导致最开始的错误,反正我是不认同的,顺序有影响,但不应该是这种报错。评论中骂声一片。不过本文情形四采用的是评论中以为博主的发言

 

最后总结:因为刚开始做,目前还没找到比较稳定的不会解析失败的配置,所以unionid既要从 code2session 接口获取保存,获取不到的话在考虑解析加密数据,用户关注公众号之后会获取到,但是在此之间存在一段时间无法获取unionid的情况。

最后:前文中的工具类是我目前最终的工具类配置,解密不是特别稳定,偶尔解析失败,乱码,请做好异常处理机制。

你可能感兴趣的:(微信交互)