From b8ad9d0b64ddf19c4d2386e3ca5e908dbc7345c1 Mon Sep 17 00:00:00 2001 From: zxc <954985706@qq.com> Date: Fri, 17 Jul 2020 14:43:40 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E7=AC=AC=E4=B8=89=E6=96=B9?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0-=E8=A7=A3=E5=AF=86bug=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/epmet/constant/ModuleConstant.java | 5 + .../com/epmet/constant/ThirdApiConstant.java | 2 +- .../constant/ThirdRunTimeInfoConstant.java | 2 + .../controller/WeChatNotifyController.java | 4 +- .../java/com/epmet/mpaes/AesDecryptUtil.java | 96 ++++++ .../java/com/epmet/mpaes/AesException.java | 59 ++++ .../main/java/com/epmet/mpaes/ByteGroup.java | 26 ++ .../java/com/epmet/mpaes/PKCS7Encoder.java | 67 +++++ .../src/main/java/com/epmet/mpaes/SHA1.java | 63 ++++ .../java/com/epmet/mpaes/WXBizMsgCrypt.java | 281 ++++++++++++++++++ .../epmet/{util => mpaes}/WXXmlToMapUtil.java | 41 ++- .../main/java/com/epmet/mpaes/XMLParse.java | 71 +++++ .../service/ComponentVerifyTicketService.java | 2 +- .../ComponentVerifyTicketServiceImpl.java | 106 +++---- .../service/impl/WarrantServiceImpl.java | 33 +- .../java/com/epmet/util/OkHttpHelper.java | 30 -- .../java/com/epmet/util/PKCS7EncoderUtil.java | 63 ---- .../java/com/epmet/util/WXBizMsgCrypt.java | 246 --------------- .../src/main/java/com/epmet/util/XmlUtil.java | 172 ----------- .../mapper/ComponentAccessTokenDao.xml | 5 +- 20 files changed, 774 insertions(+), 600 deletions(-) create mode 100644 epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/AesDecryptUtil.java create mode 100644 epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/AesException.java create mode 100644 epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/ByteGroup.java create mode 100644 epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/PKCS7Encoder.java create mode 100644 epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/SHA1.java create mode 100644 epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/WXBizMsgCrypt.java rename epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/{util => mpaes}/WXXmlToMapUtil.java (87%) create mode 100644 epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/XMLParse.java delete mode 100644 epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/util/OkHttpHelper.java delete mode 100644 epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/util/PKCS7EncoderUtil.java delete mode 100644 epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/util/WXBizMsgCrypt.java delete mode 100644 epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/util/XmlUtil.java diff --git a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/constant/ModuleConstant.java b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/constant/ModuleConstant.java index dce046fc84..19bd0fb57a 100644 --- a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/constant/ModuleConstant.java +++ b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/constant/ModuleConstant.java @@ -11,8 +11,10 @@ public interface ModuleConstant { //获得授权事件的票据 如下 String UTF8 = "UTF-8"; String MSG_SIGNATURE = "msg_signature"; + String SIGNATURE = "signature"; String TIMESTAMP = "timestamp"; String NONCE = "nonce"; + String ENCRYPT_TYPE = "encrypt_type"; String INFO_TYPE = "InfoType"; String TICKET_UNDERLINE_KEY = "component_verify_ticket"; String TICKET_KEY = "ComponentVerifyTicket"; @@ -29,6 +31,9 @@ public interface ModuleConstant { String COMPONENT_APPSECRET = "component_appsecret"; String COMPONENT_ACCESS_TOKEN = "component_access_token"; String EXPIRES_IN = "expires_in"; + String ACCOUNT_TOKEN_FLAG_ONE = "FIRST"; + String ACCOUNT_TOKEN_FLAG_TWO = "JOB"; + //获取预授权码 如下 String PRE_AUTH_CODE = "pre_auth_code"; diff --git a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/constant/ThirdApiConstant.java b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/constant/ThirdApiConstant.java index 5b05154cbc..0f0fc1eb99 100644 --- a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/constant/ThirdApiConstant.java +++ b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/constant/ThirdApiConstant.java @@ -9,7 +9,7 @@ public interface ThirdApiConstant { /** * 获取预授权码 */ - String API_CREATE_PREAUTHCODE_URL = "https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode"; + String API_CREATE_PREAUTHCODE_URL = "https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token="; /** * 使用授权码获取授权信息请求地址 diff --git a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/constant/ThirdRunTimeInfoConstant.java b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/constant/ThirdRunTimeInfoConstant.java index 2e0448a712..2e4e5e7083 100644 --- a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/constant/ThirdRunTimeInfoConstant.java +++ b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/constant/ThirdRunTimeInfoConstant.java @@ -80,4 +80,6 @@ public interface ThirdRunTimeInfoConstant { */ String TO_LIMIT = "该开放平台帐号所绑定的公众号/小程序已达上限(100 个)"; + String VERIFY_TICKET = "msgSignature = %s, timeStamp = %s, nonce = %s, encryptType = %s, signature = %s"; + } diff --git a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/controller/WeChatNotifyController.java b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/controller/WeChatNotifyController.java index 27e1051b58..36e2ee3d5e 100644 --- a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/controller/WeChatNotifyController.java +++ b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/controller/WeChatNotifyController.java @@ -1,6 +1,7 @@ package com.epmet.controller; import com.epmet.commons.tools.utils.Result; +import com.epmet.constant.ModuleConstant; import com.epmet.service.ComponentVerifyTicketService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -27,7 +28,8 @@ public class WeChatNotifyController { @PostMapping("componentaccesstoken") public Result getComponentAccessToken() { log.info("开始获取【component_access_token】......"); - componentVerifyTicketService.getComponentAccessToken(); + String accessTokenCountFlag = ModuleConstant.ACCOUNT_TOKEN_FLAG_TWO; + componentVerifyTicketService.getComponentAccessToken(accessTokenCountFlag); log.info("已成功获取到【component_access_token】......"); return new Result(); } diff --git a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/AesDecryptUtil.java b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/AesDecryptUtil.java new file mode 100644 index 0000000000..453e2add84 --- /dev/null +++ b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/AesDecryptUtil.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2018 Baidu, Inc. All Rights Reserved. + */ +package com.epmet.mpaes; + +import org.apache.commons.codec.binary.Base64; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.Charset; +import java.util.Arrays; + +/** + * JAVA AES 消息加解密 + */ +public class AesDecryptUtil { + + private static Charset CHARSET = Charset.forName("utf-8"); + private Cipher decCipher; + + /** + * 构造函数 + * + * @param encodingAesKey encodingAESKey + * + * @throws Exception 异常错误信息 + */ + public AesDecryptUtil(String encodingAesKey) throws Exception { + int encodingAesKeyLength = 43; + if (encodingAesKey.length() != encodingAesKeyLength) { + throw new Exception("ILLEGAL_AES_KEY_ERROR"); + } + byte[] aesKey = Base64.decodeBase64(encodingAesKey + "="); + + SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES"); + IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16); + Cipher encCipher = Cipher.getInstance("AES/CBC/NoPadding"); + encCipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); + + decCipher = Cipher.getInstance("AES/CBC/NoPadding"); + decCipher.init(Cipher.DECRYPT_MODE, keySpec, iv); + } + + /** + * 还原4个字节的网络字节序 + * + * @param orderBytes 字节码 + * + * @return sourceNumber + */ + private int recoverNetworkBytesOrder(byte[] orderBytes) { + int sourceNumber = 0; + int length = 4; + int number = 8; + for (int i = 0; i < length; i++) { + sourceNumber <<= number; + sourceNumber |= orderBytes[i] & 0xff; + } + return sourceNumber; + } + + /** + * 对密文进行解密 + * + * @param text 需要解密的密文 + * + * @return 解密得到的明文 + * + * @throws Exception 异常错误信息 + */ + public String decrypt(String text) + throws Exception { + byte[] original; + try { + + byte[] encrypted = Base64.decodeBase64(text); + original = decCipher.doFinal(encrypted); + } catch (Exception e) { + throw new Exception("DECRYPT_AES_ERROR"); + } + String xmlContent; + try { + // 去除补位字符 + byte[] bytes = PKCS7Encoder.decode(original); + // 分离16位随机字符串,网络字节序和ClientId + byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20); + int xmlLength = recoverNetworkBytesOrder(networkOrder); + xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET); + } catch (Exception e) { + throw new Exception("ILLEGAL_BUFFER_ERROR"); + } + return xmlContent; + } + +} \ No newline at end of file diff --git a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/AesException.java b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/AesException.java new file mode 100644 index 0000000000..e455989f9b --- /dev/null +++ b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/AesException.java @@ -0,0 +1,59 @@ +package com.epmet.mpaes; + +@SuppressWarnings("serial") +public class AesException extends Exception { + + public final static int OK = 0; + public final static int ValidateSignatureError = -40001; + public final static int ParseXmlError = -40002; + public final static int ComputeSignatureError = -40003; + public final static int IllegalAesKey = -40004; + public final static int ValidateAppidError = -40005; + public final static int EncryptAESError = -40006; + public final static int DecryptAESError = -40007; + public final static int IllegalBuffer = -40008; + //public final static int EncodeBase64Error = -40009; + //public final static int DecodeBase64Error = -40010; + //public final static int GenReturnXmlError = -40011; + + private int code; + + private static String getMessage(int code) { + switch (code) { + case ValidateSignatureError: + return "签名验证错误"; + case ParseXmlError: + return "xml解析失败"; + case ComputeSignatureError: + return "sha加密生成签名失败"; + case IllegalAesKey: + return "SymmetricKey非法"; + case ValidateAppidError: + return "appid校验失败"; + case EncryptAESError: + return "aes加密失败"; + case DecryptAESError: + return "aes解密失败"; + case IllegalBuffer: + return "解密后得到的buffer非法"; +// case EncodeBase64Error: +// return "base64加密错误"; +// case DecodeBase64Error: +// return "base64解密错误"; +// case GenReturnXmlError: +// return "xml生成失败"; + default: + return null; // cannot be + } + } + + public int getCode() { + return code; + } + + AesException(int code) { + super(getMessage(code)); + this.code = code; + } + +} diff --git a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/ByteGroup.java b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/ByteGroup.java new file mode 100644 index 0000000000..a4090d265f --- /dev/null +++ b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/ByteGroup.java @@ -0,0 +1,26 @@ +package com.epmet.mpaes; + +import java.util.ArrayList; + +class ByteGroup { + ArrayList byteContainer = new ArrayList(); + + public byte[] toBytes() { + byte[] bytes = new byte[byteContainer.size()]; + for (int i = 0; i < byteContainer.size(); i++) { + bytes[i] = byteContainer.get(i); + } + return bytes; + } + + public ByteGroup addBytes(byte[] bytes) { + for (byte b : bytes) { + byteContainer.add(b); + } + return this; + } + + public int size() { + return byteContainer.size(); + } +} diff --git a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/PKCS7Encoder.java b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/PKCS7Encoder.java new file mode 100644 index 0000000000..4c7e024f25 --- /dev/null +++ b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/PKCS7Encoder.java @@ -0,0 +1,67 @@ +/** + * 对公众平台发送给公众账号的消息加解密示例代码. + * + * @copyright Copyright (c) 1998-2014 Tencent Inc. + */ + +// ------------------------------------------------------------------------ + +package com.epmet.mpaes; + +import java.nio.charset.Charset; +import java.util.Arrays; + +/** + * 提供基于PKCS7算法的加解密接口. + */ +class PKCS7Encoder { + static Charset CHARSET = Charset.forName("utf-8"); + static int BLOCK_SIZE = 32; + + /** + * 获得对明文进行补位填充的字节. + * + * @param count 需要进行填充补位操作的明文字节个数 + * @return 补齐用的字节数组 + */ + static byte[] encode(int count) { + // 计算需要填充的位数 + int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE); + if (amountToPad == 0) { + amountToPad = BLOCK_SIZE; + } + // 获得补位所用的字符 + char padChr = chr(amountToPad); + String tmp = new String(); + for (int index = 0; index < amountToPad; index++) { + tmp += padChr; + } + return tmp.getBytes(CHARSET); + } + + /** + * 删除解密后明文的补位字符 + * + * @param decrypted 解密后的明文 + * @return 删除补位字符后的明文 + */ + static byte[] decode(byte[] decrypted) { + int pad = (int) decrypted[decrypted.length - 1]; + if (pad < 1 || pad > 32) { + pad = 0; + } + return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad); + } + + /** + * 将数字转化成ASCII码对应的字符,用于对明文进行补码 + * + * @param a 需要转化的数字 + * @return 转化得到的字符 + */ + static char chr(int a) { + byte target = (byte) (a & 0xFF); + return (char) target; + } + +} diff --git a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/SHA1.java b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/SHA1.java new file mode 100644 index 0000000000..3d45ae4499 --- /dev/null +++ b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/SHA1.java @@ -0,0 +1,63 @@ +/** + * 对公众平台发送给公众账号的消息加解密示例代码. + * + * @copyright Copyright (c) 1998-2014 Tencent Inc. + */ + +// ------------------------------------------------------------------------ + +package com.epmet.mpaes; + +import org.springframework.stereotype.Component; + +import java.security.MessageDigest; +import java.util.Arrays; + +/** + * SHA1 class + * + * 计算公众平台的消息签名接口. + */ +@Component +public class SHA1 { + + /** + * 用SHA1算法生成安全签名 + * @param token 票据 + * @param timestamp 时间戳 + * @param nonce 随机字符串 + * @param encrypt 密文 + * @return 安全签名 + * @throws AesException + */ + public static String getSHA1(String token, String timestamp, String nonce, String encrypt) throws AesException { + try { + String[] array = new String[]{token, timestamp, nonce, encrypt}; + StringBuffer sb = new StringBuffer(); + // 字符串排序 + Arrays.sort(array); + for (int i = 0; i < 4; i++) { + sb.append(array[i]); + } + String str = sb.toString(); + // SHA1签名生成 + MessageDigest md = MessageDigest.getInstance("SHA-1"); + md.update(str.getBytes()); + byte[] digest = md.digest(); + + StringBuffer hexstr = new StringBuffer(); + String shaHex = ""; + for (int i = 0; i < digest.length; i++) { + shaHex = Integer.toHexString(digest[i] & 0xFF); + if (shaHex.length() < 2) { + hexstr.append(0); + } + hexstr.append(shaHex); + } + return hexstr.toString(); + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.ComputeSignatureError); + } + } +} diff --git a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/WXBizMsgCrypt.java b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/WXBizMsgCrypt.java new file mode 100644 index 0000000000..ffe36679c5 --- /dev/null +++ b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/WXBizMsgCrypt.java @@ -0,0 +1,281 @@ +/** + * 对公众平台发送给公众账号的消息加解密示例代码. + * + * @copyright Copyright (c) 1998-2014 Tencent Inc. + */ + +// ------------------------------------------------------------------------ + +/** + * 针对org.apache.commons.codec.binary.Base64, + * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本) + * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi + */ +package com.epmet.mpaes; + +import org.apache.commons.codec.binary.Base64; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Random; + +/** + * 提供接收和推送给公众平台消息的加解密接口(UTF8编码的字符串). + *
    + *
  1. 第三方回复加密消息给公众平台
  2. + *
  3. 第三方收到公众平台发送的消息,验证消息的安全性,并对消息进行解密。
  4. + *
+ * 说明:异常java.security.InvalidKeyException:illegal Key Size的解决方案 + *
    + *
  1. 在官方网站下载JCE无限制权限策略文件(JDK7的下载地址: + * http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html
  2. + *
  3. 下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt
  4. + *
  5. 如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件
  6. + *
  7. 如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件
  8. + *
+ */ +public class WXBizMsgCrypt { + static Charset CHARSET = Charset.forName("utf-8"); + Base64 base64 = new Base64(); + byte[] aesKey; + String token; + String appId; + + /** + * 构造函数 + * @param token 公众平台上,开发者设置的token + * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey + * @param appId 公众平台appid + * + * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 + */ + public WXBizMsgCrypt(String token, String encodingAesKey, String appId) throws AesException { + if (encodingAesKey.length() != 43) { + throw new AesException(AesException.IllegalAesKey); + } + + this.token = token; + this.appId = appId; + aesKey = Base64.decodeBase64(encodingAesKey + "="); + } + + // 生成4个字节的网络字节序 + byte[] getNetworkBytesOrder(int sourceNumber) { + byte[] orderBytes = new byte[4]; + orderBytes[3] = (byte) (sourceNumber & 0xFF); + orderBytes[2] = (byte) (sourceNumber >> 8 & 0xFF); + orderBytes[1] = (byte) (sourceNumber >> 16 & 0xFF); + orderBytes[0] = (byte) (sourceNumber >> 24 & 0xFF); + return orderBytes; + } + + // 还原4个字节的网络字节序 + int recoverNetworkBytesOrder(byte[] orderBytes) { + int sourceNumber = 0; + for (int i = 0; i < 4; i++) { + sourceNumber <<= 8; + sourceNumber |= orderBytes[i] & 0xff; + } + return sourceNumber; + } + + // 随机生成16位字符串 + String getRandomStr() { + String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + Random random = new Random(); + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < 16; i++) { + int number = random.nextInt(base.length()); + sb.append(base.charAt(number)); + } + return sb.toString(); + } + + /** + * 对明文进行加密. + * + * @param text 需要加密的明文 + * @return 加密后base64编码的字符串 + * @throws AesException aes加密失败 + */ + String encrypt(String randomStr, String text) throws AesException { + ByteGroup byteCollector = new ByteGroup(); + byte[] randomStrBytes = randomStr.getBytes(CHARSET); + byte[] textBytes = text.getBytes(CHARSET); + byte[] networkBytesOrder = getNetworkBytesOrder(textBytes.length); + byte[] appidBytes = appId.getBytes(CHARSET); + + // randomStr + networkBytesOrder + text + appid + byteCollector.addBytes(randomStrBytes); + byteCollector.addBytes(networkBytesOrder); + byteCollector.addBytes(textBytes); + byteCollector.addBytes(appidBytes); + + // ... + pad: 使用自定义的填充方式对明文进行补位填充 + byte[] padBytes = PKCS7Encoder.encode(byteCollector.size()); + byteCollector.addBytes(padBytes); + + // 获得最终的字节流, 未加密 + byte[] unencrypted = byteCollector.toBytes(); + + try { + // 设置加密模式为AES的CBC模式 + Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); + SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES"); + IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16); + cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); + + // 加密 + byte[] encrypted = cipher.doFinal(unencrypted); + + // 使用BASE64对加密后的字符串进行编码 + String base64Encrypted = base64.encodeToString(encrypted); + + return base64Encrypted; + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.EncryptAESError); + } + } + + /** + * 对密文进行解密. + * + * @param text 需要解密的密文 + * @return 解密得到的明文 + * @throws AesException aes解密失败 + */ + String decrypt(String text) throws AesException { + byte[] original; + try { + // 设置解密模式为AES的CBC模式 + Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); + SecretKeySpec key_spec = new SecretKeySpec(aesKey, "AES"); + IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16)); + cipher.init(Cipher.DECRYPT_MODE, key_spec, iv); + + // 使用BASE64对密文进行解码 + byte[] encrypted = Base64.decodeBase64(text); + + // 解密 + original = cipher.doFinal(encrypted); + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.DecryptAESError); + } + + String xmlContent, from_appid; + try { + // 去除补位字符 + byte[] bytes = PKCS7Encoder.decode(original); + + // 分离16位随机字符串,网络字节序和AppId + byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20); + + int xmlLength = recoverNetworkBytesOrder(networkOrder); + + xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET); + from_appid = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), + CHARSET); + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.IllegalBuffer); + } + + // appid不相同的情况 + if (!from_appid.equals(appId)) { + throw new AesException(AesException.ValidateAppidError); + } + return xmlContent; + + } + + /** + * 将公众平台回复用户的消息加密打包. + *
    + *
  1. 对要发送的消息进行AES-CBC加密
  2. + *
  3. 生成安全签名
  4. + *
  5. 将消息密文和安全签名打包成xml格式
  6. + *
+ * + * @param replyMsg 公众平台待回复用户的消息,xml格式的字符串 + * @param timeStamp 时间戳,可以自己生成,也可以用URL参数的timestamp + * @param nonce 随机串,可以自己生成,也可以用URL参数的nonce + * + * @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串 + * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 + */ + public String encryptMsg(String replyMsg, String timeStamp, String nonce) throws AesException { + // 加密 + String encrypt = encrypt(getRandomStr(), replyMsg); + + // 生成安全签名 + if (timeStamp == "") { + timeStamp = Long.toString(System.currentTimeMillis()); + } + + String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt); + + // System.out.println("发送给平台的签名是: " + signature[1].toString()); + // 生成发送的xml + String result = XMLParse.generate(encrypt, signature, timeStamp, nonce); + return result; + } + + /** + * 检验消息的真实性,并且获取解密后的明文. + *
    + *
  1. 利用收到的密文生成安全签名,进行签名验证
  2. + *
  3. 若验证通过,则提取xml中的加密消息
  4. + *
  5. 对消息进行解密
  6. + *
+ * + * @param msgSignature 签名串,对应URL参数的msg_signature + * @param timeStamp 时间戳,对应URL参数的timestamp + * @param nonce 随机串,对应URL参数的nonce + * @param postData 密文,对应POST请求的数据 + * + * @return 解密后的原文 + * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 + */ + public String decryptMsg(String msgSignature, String timeStamp, String nonce, String postData) + throws AesException { + // 提取密文 + String encrypt = XMLParse.extract(postData); + + // 验证安全签名 + String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt); + if (!signature.equals(msgSignature)) { + throw new AesException(AesException.ValidateSignatureError); + } + // 解密 + String result = decrypt(encrypt); + return result; + } + + /** + * 验证URL + * @param msgSignature 签名串,对应URL参数的msg_signature + * @param timeStamp 时间戳,对应URL参数的timestamp + * @param nonce 随机串,对应URL参数的nonce + * @param echoStr 随机串,对应URL参数的echostr + * + * @return 解密之后的echostr + * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 + */ + public String verifyUrl(String msgSignature, String timeStamp, String nonce, String echoStr) + throws AesException { + String signature = SHA1.getSHA1(token, timeStamp, nonce, echoStr); + + if (!signature.equals(msgSignature)) { + throw new AesException(AesException.ValidateSignatureError); + } + + String result = decrypt(echoStr); + return result; + } + +} \ No newline at end of file diff --git a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/util/WXXmlToMapUtil.java b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/WXXmlToMapUtil.java similarity index 87% rename from epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/util/WXXmlToMapUtil.java rename to epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/WXXmlToMapUtil.java index e7b119ed20..d405bea43f 100644 --- a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/util/WXXmlToMapUtil.java +++ b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/WXXmlToMapUtil.java @@ -1,5 +1,8 @@ -package com.epmet.util; +package com.epmet.mpaes; +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.naming.NoNameCoder; +import com.thoughtworks.xstream.io.xml.DomDriver; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.DocumentHelper; @@ -16,6 +19,7 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.StringReader; import java.io.StringWriter; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -226,5 +230,40 @@ public class WXXmlToMapUtil { return null; } } + + /** + * 以格式化的方式输出XML + * + * @param obj + * @return + */ + public static String toXml(Object obj) { + XStream xstream = getXStream(); + // 识别obj类中的注解 + xstream.processAnnotations(obj.getClass()); + // 设置JavaBean的类别名 + xstream.aliasType("xml", obj.getClass()); + return xstream.toXML(obj); + } + + /** + * 转换不带CDDATA的XML + * + * @return + * @ + */ + private static XStream getXStream() { + // 实例化XStream基本对象 + XStream xstream = new XStream(new DomDriver(StandardCharsets.UTF_8.name(), new NoNameCoder() { + // 不对特殊字符进行转换,避免出现重命名字段时的“双下划线” + @Override + public String encodeNode(String name) { + return name; + } + })); + // 忽视XML与JAVABEAN转换时,XML中的字段在JAVABEAN中不存在的部分 + xstream.ignoreUnknownElements(); + return xstream; + } } diff --git a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/XMLParse.java b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/XMLParse.java new file mode 100644 index 0000000000..11ca29eb5c --- /dev/null +++ b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/mpaes/XMLParse.java @@ -0,0 +1,71 @@ +/** + * 对公众平台发送给公众账号的消息加解密示例代码. + * + * @copyright Copyright (c) 1998-2014 Tencent Inc. + */ + +// ------------------------------------------------------------------------ + +package com.epmet.mpaes; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.StringReader; + +/** + * XMLParse class + * + * 提供提取消息格式中的密文及生成回复消息格式的接口. + */ +public class XMLParse { + + /** + * 提取出xml数据包中的加密消息 + * @param xmltext 待提取的xml字符串 + * @return 提取出的加密消息字符串 + * @throws AesException + */ + public static String extract(String xmltext) throws AesException { +// Object[] result = new Object[3]; + try { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + StringReader sr = new StringReader(xmltext); + InputSource is = new InputSource(sr); + Document document = db.parse(is); + + Element root = document.getDocumentElement(); + NodeList nodelist1 = root.getElementsByTagName("Encrypt"); + NodeList nodelist2 = root.getElementsByTagName("ToUserName"); +// result[0] = 0; + String result = nodelist1.item(0).getTextContent(); +// result[2] = nodelist2.item(0).getTextContent(); + return result; + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.ParseXmlError); + } + } + + /** + * 生成xml消息 + * @param encrypt 加密后的消息密文 + * @param signature 安全签名 + * @param timestamp 时间戳 + * @param nonce 随机字符串 + * @return 生成的xml字符串 + */ + public static String generate(String encrypt, String signature, String timestamp, String nonce) { + + String format = "\n" + "\n" + + "\n" + + "%3$s\n" + "\n" + ""; + return String.format(format, encrypt, signature, timestamp, nonce); + + } +} diff --git a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/service/ComponentVerifyTicketService.java b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/service/ComponentVerifyTicketService.java index 2fb00e3322..6071409e47 100644 --- a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/service/ComponentVerifyTicketService.java +++ b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/service/ComponentVerifyTicketService.java @@ -21,7 +21,7 @@ public interface ComponentVerifyTicketService { * 令牌(component_access_token)是第三方平台接口的调用凭据。令牌的获取是有限制的,每个令牌的有效期为 2 小时 * @author zxc */ - void getComponentAccessToken(); + void getComponentAccessToken(String accessTokenCountFlag); /** * @Description 每个预授权码有效期为 10 分钟。需要先获取令牌才能调用 获取预授权码 diff --git a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/service/impl/ComponentVerifyTicketServiceImpl.java b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/service/impl/ComponentVerifyTicketServiceImpl.java index ba736b599d..8c33163d28 100644 --- a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/service/impl/ComponentVerifyTicketServiceImpl.java +++ b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/service/impl/ComponentVerifyTicketServiceImpl.java @@ -15,12 +15,10 @@ import com.epmet.dto.result.AuthCodeResultDTO; import com.epmet.dto.result.AuthorizationInfoResultDTO; import com.epmet.dto.result.CreateOpenResultDTO; import com.epmet.dto.result.WillOverDueResultDTO; +import com.epmet.mpaes.WXBizMsgCrypt; +import com.epmet.mpaes.WXXmlToMapUtil; import com.epmet.redis.RedisThird; import com.epmet.service.ComponentVerifyTicketService; -import com.epmet.util.WXBizMsgCrypt; -import com.epmet.util.WXXmlToMapUtil; -import com.epmet.util.XmlUtil; -import com.github.pagehelper.util.StringUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.BeanUtils; @@ -34,11 +32,9 @@ import javax.servlet.http.HttpServletResponse; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.lang.reflect.Field; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneOffset; import java.util.*; +import static com.epmet.constant.ModuleConstant.COMPONENT_ACCESS_TOKEN; import static com.epmet.constant.ThirdRunTimeInfoConstant.*; /** @@ -107,27 +103,17 @@ public class ComponentVerifyTicketServiceImpl implements ComponentVerifyTicketSe String timeStamp = request.getParameter(ModuleConstant.TIMESTAMP); // 随机数 String nonce = request.getParameter(ModuleConstant.NONCE); + String encryptType = request.getParameter(ModuleConstant.ENCRYPT_TYPE); + String signature = request.getParameter(ModuleConstant.SIGNATURE); + log.info(String.format(ThirdRunTimeInfoConstant.VERIFY_TICKET,msgSignature,timeStamp,nonce,encryptType,signature)); // 从请求中读取整个post数据 InputStream inputStream; String postData = null; inputStream = request.getInputStream(); postData= IOUtils.toString(inputStream,ModuleConstant.UTF8); - //从XML中获取标签内的密文文本 - String encrypt = XmlUtil.toXml(postData); - log.info(String.format(ThirdRunTimeInfoConstant.ENCRYPT,encrypt)); - //格式化密文文本,否则没有标签,会解密失败,参考官方的加解密代码JAVA版本 - String format = ""; - String fromXML = String.format(format, encrypt); - - String msg = ""; //解密后的明文 - WXBizMsgCrypt wxcpt; - if(StringUtil.isEmpty(encrypt)) { - msg = fromXML; - } else { - wxcpt = new WXBizMsgCrypt(token,aesKey, componentAppId); - // 解密消息 - msg = wxcpt.decryptMsg(msgSignature, timeStamp, nonce, fromXML); - } + log.info("postData = "+postData); + WXBizMsgCrypt wxBizMsgCrypt = new WXBizMsgCrypt(token,aesKey,componentAppId); + String msg = wxBizMsgCrypt.decryptMsg(msgSignature, timeStamp, nonce, postData); log.info(String.format(ThirdRunTimeInfoConstant.MSG,msg)); // 将xml转为map Map result = WXXmlToMapUtil.xmlToMap(msg); @@ -171,7 +157,8 @@ public class ComponentVerifyTicketServiceImpl implements ComponentVerifyTicketSe log.info(ThirdRunTimeInfoConstant.END_TICKET); Integer tokenCount = componentAccessTokenDao.selectAccessTokenCount(); if (tokenCount == NumConstant.ZERO){ - this.getComponentAccessToken(); + String accessTokenCountFlag = ModuleConstant.ACCOUNT_TOKEN_FLAG_ONE; + this.getComponentAccessToken(accessTokenCountFlag); } return ModuleConstant.SUCCESS; } @@ -179,44 +166,40 @@ public class ComponentVerifyTicketServiceImpl implements ComponentVerifyTicketSe /** * @Description 定时获取 (令牌,component_access_token) 第三方与微信交互使用的component_access_token * 每十分钟执行一次,判断是否有马上超时的(15分钟以内算马上超时) - * @param + * @param accessTokenCountFlag 表里的 component_access_token的数量 * @author zxc */ @Transactional(rollbackFor = Exception.class) @Override - public void getComponentAccessToken() { + public void getComponentAccessToken(String accessTokenCountFlag) { log.info(ThirdRunTimeInfoConstant.START_GET_COMPONENT_ACCESS_TOKEN); Map reMap; //距离超时时间小于15分钟的数量 Integer tokenCount = componentAccessTokenDao.selectWillOverTokenCount(); - if (tokenCount > NumConstant.ZERO) { - try { - String componentVerifyTicket = redisThird.getComponentVerifyTicket(); - JSONObject jsonObject = new JSONObject(); - jsonObject.put(ModuleConstant.COMPONENT_APPID, componentAppId); - jsonObject.put(ModuleConstant.COMPONENT_APPSECRET, appSecret); - jsonObject.put(ModuleConstant.TICKET_UNDERLINE_KEY, componentVerifyTicket); - String post = HttpClientManager.getInstance().sendPostByJSON(ThirdApiConstant.API_COMPONENT_TOKEN_URL, JSON.toJSONString(jsonObject)).getData(); - HashMap hashMap = JSON.parseObject(post, HashMap.class); - String componentAccessToken = hashMap.get(ModuleConstant.COMPONENT_ACCESS_TOKEN); - String expiresIn = hashMap.get(ModuleConstant.EXPIRES_IN); - Date expiresInTime = this.countExpirationTime(expiresIn); - if (StringUtils.isNotEmpty(componentAccessToken)) { - //令牌信息存DB - ComponentAccessTokenFormDTO formDTO = new ComponentAccessTokenFormDTO(); - formDTO.setComponentAccessToken(componentAccessToken); - formDTO.setExpiresInTime(expiresInTime); - //先逻辑删,在插入 - log.info(ThirdRunTimeInfoConstant.START_DELETE_COMPONENT_ACCESS_TOKEN); - componentAccessTokenDao.updateOldComponentAccessToken(); - componentAccessTokenDao.insertComponentAccessToken(formDTO); - //存缓存 - redisThird.setComponentAccessToken(componentAccessToken); - } else { - throw new RenException(ThirdRunTimeInfoConstant.FAILURE_ACCESS_TOKEN); - } - } catch (Exception e) { - e.printStackTrace(); + if ((tokenCount > NumConstant.ZERO && accessTokenCountFlag.equals(ModuleConstant.ACCOUNT_TOKEN_FLAG_TWO)) || accessTokenCountFlag.equals(ModuleConstant.ACCOUNT_TOKEN_FLAG_ONE)) { + String componentVerifyTicket = redisThird.getComponentVerifyTicket(); + JSONObject jsonObject = new JSONObject(); + jsonObject.put(ModuleConstant.COMPONENT_APPID, componentAppId); + jsonObject.put(ModuleConstant.COMPONENT_APPSECRET, appSecret); + jsonObject.put(ModuleConstant.TICKET_UNDERLINE_KEY, componentVerifyTicket); + String post = HttpClientManager.getInstance().sendPostByJSON(ThirdApiConstant.API_COMPONENT_TOKEN_URL, JSON.toJSONString(jsonObject)).getData(); + Map hashMap = JSON.parseObject(post, Map.class); + String componentAccessToken = hashMap.get(COMPONENT_ACCESS_TOKEN).toString(); + Integer expiresIn = (Integer) hashMap.get(ModuleConstant.EXPIRES_IN); + Date expiresInTime = this.countExpirationTime(expiresIn.toString()); + if (StringUtils.isNotEmpty(componentAccessToken)) { + //令牌信息存DB + ComponentAccessTokenFormDTO formDTO = new ComponentAccessTokenFormDTO(); + formDTO.setComponentAccessToken(componentAccessToken); + formDTO.setExpiresInTime(expiresInTime); + //先逻辑删,在插入 + log.info(ThirdRunTimeInfoConstant.START_DELETE_COMPONENT_ACCESS_TOKEN); + componentAccessTokenDao.updateOldComponentAccessToken(); + componentAccessTokenDao.insertComponentAccessToken(formDTO); + //存缓存 + redisThird.setComponentAccessToken(componentAccessToken); + } else { + throw new RenException(ThirdRunTimeInfoConstant.FAILURE_ACCESS_TOKEN); } log.info(ThirdRunTimeInfoConstant.SUCCESS_ACCESS_TOKEN); } @@ -238,10 +221,10 @@ public class ComponentVerifyTicketServiceImpl implements ComponentVerifyTicketSe jsonObject.put(ModuleConstant.COMPONENT_APPID, componentAppId); String post = HttpClientManager.getInstance().sendPostByJSON(ThirdApiConstant.API_CREATE_PREAUTHCODE_URL + accessToken, JSON.toJSONString(jsonObject)).getData(); log.info(String.format(POST_RESULT,post)); - HashMap hashMap = JSON.parseObject(post, HashMap.class); - preAuthCode = hashMap.get(ModuleConstant.PRE_AUTH_CODE); - String expiresIn = hashMap.get(ModuleConstant.EXPIRES_IN); - Date expiresInTime = this.countExpirationTime(expiresIn); + Map hashMap = JSON.parseObject(post, Map.class); + preAuthCode = hashMap.get(ModuleConstant.PRE_AUTH_CODE).toString(); + Integer expiresIn = (Integer) hashMap.get(ModuleConstant.EXPIRES_IN); + Date expiresInTime = this.countExpirationTime(expiresIn.toString()); if (StringUtils.isNotEmpty(preAuthCode)) { //预授权码 存DB PreAuthTokenFormDTO formDTO = new PreAuthTokenFormDTO(); @@ -624,10 +607,9 @@ public class ComponentVerifyTicketServiceImpl implements ComponentVerifyTicketSe * @author zxc */ public Date countExpirationTime(String expiresIn){ - long now = LocalDateTime.now().toEpochSecond(ZoneOffset.of(NumConstant.POSITIVE_EIGHT_STR)); - long expiresInTime = now + Long.valueOf(expiresIn); - LocalDateTime localDateTime = Instant.ofEpochMilli(expiresInTime).atZone(ZoneOffset.ofHours(NumConstant.EIGHT)).toLocalDateTime(); - Date date = Date.from(localDateTime.atZone(ZoneOffset.ofHours(NumConstant.EIGHT)).toInstant()); + Date date = new Date(); + long l = date.getTime() + Long.valueOf(expiresIn); + date.setTime(l); return date; } } diff --git a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/service/impl/WarrantServiceImpl.java b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/service/impl/WarrantServiceImpl.java index 1db2652bfa..c0dc9e2caf 100644 --- a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/service/impl/WarrantServiceImpl.java +++ b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/service/impl/WarrantServiceImpl.java @@ -11,10 +11,9 @@ import com.epmet.dto.form.CodeAuditRecordFormDTO; import com.epmet.dto.result.CustomerIdAndClientResultDTO; import com.epmet.dto.result.TemplateAndAppIdResultDTO; import com.epmet.exception.AesException; +import com.epmet.mpaes.WXBizMsgCrypt; +import com.epmet.mpaes.WXXmlToMapUtil; import com.epmet.service.WarrantService; -import com.epmet.util.WXBizMsgCrypt; -import com.epmet.util.WXXmlToMapUtil; -import com.epmet.util.XmlUtil; import com.github.pagehelper.util.StringUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -26,7 +25,6 @@ import org.springframework.transaction.annotation.Transactional; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.util.Map; @@ -68,6 +66,8 @@ public class WarrantServiceImpl implements WarrantService { @Transactional(rollbackFor = Exception.class) @Override public void acceptMessageAndEvent(HttpServletRequest request, String appid, HttpServletResponse response)throws IOException, DocumentException, AesException { + log.info("request:"+request); + log.info("appId:"+ appid); request.setCharacterEncoding(ModuleConstant.UTF8); String msgSignature = request.getParameter(ModuleConstant.MSG_SIGNATURE); String timeStamp = request.getParameter(ModuleConstant.TIMESTAMP); @@ -79,26 +79,19 @@ public class WarrantServiceImpl implements WarrantService { String postData = null; inputStream = request.getInputStream(); postData= IOUtils.toString(inputStream,ModuleConstant.UTF8); - //从XML中获取标签内的密文文本 - String encrypt = XmlUtil.toXml(postData); - log.info(String.format(ThirdRunTimeInfoConstant.ENCRYPT,encrypt)); - //格式化密文文本,否则没有标签,会解密失败,参考官方的加解密代码JAVA版本 - String format = ""; - String fromXML = String.format(format, encrypt); - - String msg = ""; //解密后的明文 - WXBizMsgCrypt wxcpt; - if(StringUtil.isEmpty(encrypt)) { - msg = fromXML; - } else { - wxcpt = new WXBizMsgCrypt(token,aesKey, componentAppId); - // 解密消息 - msg = wxcpt.decryptMsg(msgSignature, timeStamp, nonce, fromXML); + WXBizMsgCrypt wxBizMsgCrypt = null; + String msg = null; + try { + wxBizMsgCrypt = new WXBizMsgCrypt(token,aesKey,componentAppId); + msg = wxBizMsgCrypt.decryptMsg(msgSignature, timeStamp, nonce, postData); + } catch (com.epmet.mpaes.AesException e) { + e.printStackTrace(); } log.info(String.format(ThirdRunTimeInfoConstant.MSG,msg)); // 将xml转为map Map result = WXXmlToMapUtil.multilayerXmlToMap(msg); - CodeAuditRecordFormDTO codeAuditRecord = componentVerifyTicketService.mapToEntity(result, CodeAuditRecordFormDTO.class); + Map xml = (Map) result.get("xml"); + CodeAuditRecordFormDTO codeAuditRecord = componentVerifyTicketService.mapToEntity(xml, CodeAuditRecordFormDTO.class); String toUserName = codeAuditRecord.getToUserName();//小程序原始ID CustomerIdAndClientResultDTO customerIdAndClientResultDTO = miniInfoDao.selectCustomerIdAndClientByToUserName(toUserName); String clientType = customerIdAndClientResultDTO.getClientType(); diff --git a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/util/OkHttpHelper.java b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/util/OkHttpHelper.java deleted file mode 100644 index 0c11244db8..0000000000 --- a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/util/OkHttpHelper.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.epmet.util; - -import okhttp3.*; -import org.springframework.stereotype.Component; - -import java.io.IOException; - -/** - * @Author zxc - * @CreateTime 2020/7/7 17:39 - */ -@Component -public class OkHttpHelper { - - public static final MediaType JSON = MediaType.get("application/json; charset=utf-8"); - - OkHttpClient client = new OkHttpClient(); - - public String post(String url, String json) throws IOException { - RequestBody body = RequestBody.create(JSON, json); - Request request = new Request.Builder() - .url(url) - .post(body) - .build(); - try (Response response = client.newCall(request).execute()) { - return response.body().string(); - } - } - -} diff --git a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/util/PKCS7EncoderUtil.java b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/util/PKCS7EncoderUtil.java deleted file mode 100644 index 1cfac333af..0000000000 --- a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/util/PKCS7EncoderUtil.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.epmet.util; - -import com.epmet.commons.tools.constant.NumConstant; - -import java.nio.charset.Charset; -import java.util.Arrays; - -/** - * @Author zxc - * @CreateTime 2020/7/6 10:45 - */ -public class PKCS7EncoderUtil { - - static Charset CHARSET = Charset.forName("utf-8"); - static int BLOCK_SIZE = 32; - - /** - * 获得对明文进行补位填充的字节. - * - * @param count 需要进行填充补位操作的明文字节个数 - * @return 补齐用的字节数组 - */ - static byte[] encode(int count) { - // 计算需要填充的位数 - int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE); - if (amountToPad == NumConstant.ZERO) { - amountToPad = BLOCK_SIZE; - } - // 获得补位所用的字符 - char padChr = chr(amountToPad); - String tmp = new String(); - for (int index = NumConstant.ZERO; index < amountToPad; index++) { - tmp += padChr; - } - return tmp.getBytes(CHARSET); - } - - /** - * 删除解密后明文的补位字符 - * - * @param decrypted 解密后的明文 - * @return 删除补位字符后的明文 - */ - static byte[] decode(byte[] decrypted) { - int pad = (int) decrypted[decrypted.length - 1]; - if (pad < NumConstant.ONE || pad > 32) { - pad = NumConstant.ZERO; - } - return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad); - } - - /** - * 将数字转化成ASCII码对应的字符,用于对明文进行补码 - * - * @param a 需要转化的数字 - * @return 转化得到的字符 - */ - static char chr(int a) { - byte target = (byte) (a & 0xFF); - return (char) target; - } - -} diff --git a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/util/WXBizMsgCrypt.java b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/util/WXBizMsgCrypt.java deleted file mode 100644 index 332e363eaa..0000000000 --- a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/util/WXBizMsgCrypt.java +++ /dev/null @@ -1,246 +0,0 @@ -package com.epmet.util; - -import com.epmet.exception.AesException; -import me.chanjar.weixin.common.util.crypto.PKCS7Encoder; -import org.apache.commons.codec.binary.Base64; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; -import org.xml.sax.InputSource; - -import java.io.StringReader; -import java.nio.charset.Charset; -import java.security.MessageDigest; -import java.util.Arrays; - -import javax.crypto.Cipher; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; - -/** - * 提供接收和推送给公众平台消息的加解密接口(UTF8编码的字符串). - *
    *
  1. 第三方回复加密消息给公众平台
  2. *
  3. 第三方收到公众平台发送的消息,验证消息的安全性,并对消息进行解密。
  4. - *
- * 说明:异常java.security.InvalidKeyException:illegal Key Size的解决方案 - *
    - *
  1. 在官方网站下载JCE无限制权限策略文件(JDK7的下载地址: * - * http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html
  2. - *
  3. 下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt
  4. - *
  5. 如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件
  6. - *
  7. 如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件
  8. - * - *
- */ - -/** - * @Author zxc - * @CreateTime 2020/7/6 9:51 - */ -public class WXBizMsgCrypt { - static Charset CHARSET = Charset.forName("utf-8"); - Base64 base64 = new Base64(); - byte[] aesKey; - String token; - String appId; - - /** - * 构造函数 - * @param token 公众平台上,开发者设置的token - * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey - * @param appId 公众平台appid - * - * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 - */ - public WXBizMsgCrypt(String token, String encodingAesKey, String appId) throws AesException { - if (encodingAesKey.length() != 43) { - throw new AesException(AesException.IllegalAesKey); - } - - this.token = token; - this.appId = appId; - aesKey = Base64.decodeBase64(encodingAesKey + "="); - } - - // 还原4个字节的网络字节序 - int recoverNetworkBytesOrder(byte[] orderBytes) { - int sourceNumber = 0; - for (int i = 0; i < 4; i++) { - sourceNumber <<= 8; - sourceNumber |= orderBytes[i] & 0xff; - } - return sourceNumber; - } - - /** - * 对密文进行解密. - * @param text 需要解密的密文 - * @return 解密得到的明文 - * @throws AesException aes解密失败 - */ - String decrypt(String text) throws AesException { - byte[] original; - try { - // 设置解密模式为AES的CBC模式 - Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); - SecretKeySpec key_spec = new SecretKeySpec(aesKey, "AES"); - IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16)); - cipher.init(Cipher.DECRYPT_MODE, key_spec, iv); - - // 使用BASE64对密文进行解码 - byte[] encrypted = Base64.decodeBase64(text); - - // 解密 - original = cipher.doFinal(encrypted); - } catch (Exception e) { - e.printStackTrace(); - throw new AesException(AesException.DecryptAESError); - } - - String xmlContent, from_appid; - try { - // 去除补位字符 - byte[] bytes = PKCS7Encoder.decode(original); - - // 分离16位随机字符串,网络字节序和AppId - byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20); - - int xmlLength = recoverNetworkBytesOrder(networkOrder); - - xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET); - from_appid = - new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET); - } catch (Exception e) { - e.printStackTrace(); - throw new AesException(AesException.IllegalBuffer); - } - - // appid不相同的情况 - if (!from_appid.equals(appId)) { - throw new AesException(AesException.ValidateSignatureError); - } - return xmlContent; - - } - - /** - * * 检验消息的真实性,并且获取解密后的明文. - *
    - *
  1. 利用收到的密文生成安全签名,进行签名验证
  2. - *
  3. 若验证通过,则提取xml中的加密消息
  4. - *
  5. 对消息进行解密
  6. - *
- * - * @param msgSignature 签名串,对应URL参数的msg_signature - * @param timeStamp 时间戳,对应URL参数的timestamp - * @param nonce 随机串,对应URL参数的nonce - * @param postData 密文,对应POST请求的数据 - * @return 解密后的原文 - * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 - */ - public String decryptMsg(String msgSignature, String timeStamp, String nonce, String postData) - throws AesException { - - // 密钥,公众账号的app secret - // 提取密文 - Object[] encrypt = extract(postData); - - // 验证安全签名 - String signature = getSHA1(token, timeStamp, nonce, encrypt[1].toString()); - - // 和URL中的签名比较是否相等 - // System.out.println("第三方收到URL中的签名:" + msg_sign); - // System.out.println("第三方校验签名:" + signature); - if (!signature.equals(msgSignature)) { - throw new AesException(AesException.ValidateSignatureError); - } - - // 解密 - String result = decrypt(encrypt[1].toString()); - return result; - } - - /** - * 提取出xml数据包中的加密消息 - * @param xmltext 待提取的xml字符串 - * @return 提取出的加密消息字符串 - * @throws AesException - */ - public static Object[] extract(String xmltext) throws AesException { - Object[] result = new Object[3]; - try { - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); - dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); - dbf.setXIncludeAware(false); - dbf.setExpandEntityReferences(false); - DocumentBuilder db = dbf.newDocumentBuilder(); - StringReader sr = new StringReader(xmltext); - InputSource is = new InputSource(sr); - Document document = db.parse(is); - - Element root = document.getDocumentElement(); - NodeList nodelist1 = root.getElementsByTagName("Encrypt"); - NodeList nodelist2 = root.getElementsByTagName("ToUserName"); - result[0] = 0; - result[1] = nodelist1.item(0).getTextContent(); - - //注意这里,获取ticket中的xml里面没有ToUserName这个元素,官网原示例代码在这里会报空 - //空指针,所以需要处理一下 - if (nodelist2 != null) { - if (nodelist2.item(0) != null) { - result[2] = nodelist2.item(0).getTextContent(); - } - } - return result; - } catch (Exception e) { - e.printStackTrace(); - throw new AesException(AesException.ParseXmlError); - } - } - - /** - * 用SHA1算法生成安全签名 - * @param token 票据 - * @param timestamp 时间戳 - * @param nonce 随机字符串 - * @param encrypt 密文 - * @return 安全签名 - * @throws - * AesException - */ - public static String getSHA1(String token, String timestamp, String nonce, String encrypt) - throws AesException { - try { - String[] array = new String[]{token, timestamp, nonce, encrypt}; - StringBuffer sb = new StringBuffer(); - // 字符串排序 - Arrays.sort(array); - for (int i = 0; i < 4; i++) { - sb.append(array[i]); - } - String str = sb.toString(); - // SHA1签名生成 - MessageDigest md = MessageDigest.getInstance("SHA-1"); - md.update(str.getBytes()); - byte[] digest = md.digest(); - - StringBuffer hexstr = new StringBuffer(); - String shaHex = ""; - for (int i = 0; i < digest.length; i++) { - shaHex = Integer.toHexString(digest[i] & 0xFF); - if (shaHex.length() < 2) { - hexstr.append(0); - } - hexstr.append(shaHex); - } - return hexstr.toString(); - } catch (Exception e) { - e.printStackTrace(); - throw new AesException(AesException.ComputeSignatureError); - } - } -} diff --git a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/util/XmlUtil.java b/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/util/XmlUtil.java deleted file mode 100644 index 1fdc3d0307..0000000000 --- a/epmet-module/epmet-third/epmet-third-server/src/main/java/com/epmet/util/XmlUtil.java +++ /dev/null @@ -1,172 +0,0 @@ -package com.epmet.util; -import com.google.common.collect.Lists; -import com.thoughtworks.xstream.XStream; -import com.thoughtworks.xstream.core.util.QuickWriter; -import com.thoughtworks.xstream.io.HierarchicalStreamWriter; -import com.thoughtworks.xstream.io.naming.NoNameCoder; -import com.thoughtworks.xstream.io.xml.CompactWriter; -import com.thoughtworks.xstream.io.xml.DomDriver; -import com.thoughtworks.xstream.io.xml.PrettyPrintWriter; -import com.thoughtworks.xstream.io.xml.XppDriver; - -import java.io.StringWriter; -import java.io.Writer; -import java.nio.charset.StandardCharsets; -import java.util.List; - -public class XmlUtil { - - /** - * 转换不带CDDATA的XML - * - * @return - * @ - */ - private static XStream getXStream() { - // 实例化XStream基本对象 - XStream xstream = new XStream(new DomDriver(StandardCharsets.UTF_8.name(), new NoNameCoder() { - // 不对特殊字符进行转换,避免出现重命名字段时的“双下划线” - @Override - public String encodeNode(String name) { - return name; - } - })); - // 忽视XML与JAVABEAN转换时,XML中的字段在JAVABEAN中不存在的部分 - xstream.ignoreUnknownElements(); - return xstream; - } - - - - /** - * 转换带CDDATA的XML - * - * @return - * @ - */ - private static XStream getXStreamWithCData(List ignoreCDATA) { - // 实例化XStream扩展对象 - XStream xstream = new XStream(new XppDriver() { - // 扩展xstream,使其支持CDATA块 - @Override - public HierarchicalStreamWriter createWriter(Writer out) { - return new PrettyPrintWriter(out) { - boolean cdata = true; - // 不对特殊字符进行转换,避免出现重命名字段时的“双下划线” - @Override - public String encodeNode(String name) { - if(!ignoreCDATA.isEmpty()){ - for(String str:ignoreCDATA){ - if(str.equals(name)){ - cdata=false; - return name; - } - } - } - cdata=true; - return name; - } - // 对xml节点的转换都增加CDATA标记 - @Override - protected void writeText(QuickWriter writer, String text) { - if (cdata) { - writer.write(""); - } else { - writer.write(text); - } - } - - - }; - } - }); - // 忽视XML与JAVABEAN转换时,XML中的字段在JAVABEAN中不存在的部分 - xstream.ignoreUnknownElements(); - return xstream; - } - - /** - * 以压缩的方式输出XML - * - * @param obj - * @return - */ - public static String toCompressXml(Object obj) { - XStream xstream = getXStream(); - StringWriter sw = new StringWriter(); - // 识别obj类中的注解 - xstream.processAnnotations(obj.getClass()); - // 设置JavaBean的类别名 - xstream.aliasType("xml", obj.getClass()); - xstream.marshal(obj, new CompactWriter(sw)); - return sw.toString(); - } - - /** - * 以格式化的方式输出XML - * - * @param obj - * @return - */ - public static String toXml(Object obj) { - XStream xstream = getXStream(); - // 识别obj类中的注解 - xstream.processAnnotations(obj.getClass()); - // 设置JavaBean的类别名 - xstream.aliasType("xml", obj.getClass()); - return xstream.toXML(obj); - } - - /** - * 转换成JavaBean - * - * @param xmlStr - * @param cls - * @return - */ - @SuppressWarnings("unchecked") - public static T toBean(String xmlStr, Class cls) { - XStream xstream = getXStream(); - // 识别cls类中的注解 - xstream.processAnnotations(cls); - // 设置JavaBean的类别名 - xstream.aliasType("xml", cls); - T t = (T) xstream.fromXML(xmlStr); - return t; - } - - /** - * 以格式化的方式输出XML - * - * @param obj - * @return - */ - public static String toXmlWithCData(Object obj,List ignoreCDAA) { - XStream xstream = getXStreamWithCData(ignoreCDAA); - // 识别obj类中的注解 - xstream.processAnnotations(obj.getClass()); - // 设置JavaBean的类别名 - xstream.aliasType("xml", obj.getClass()); - return xstream.toXML(obj); - } - - /** - * 转换成JavaBean - * - * @param xmlStr - * @param cls - * @return - */ - @SuppressWarnings("unchecked") - public static T toBeanWithCData(String xmlStr, Class cls) { - XStream xstream = getXStreamWithCData(null); - // 识别cls类中的注解 - xstream.processAnnotations(cls); - // 设置JavaBean的类别名 - xstream.alias("xml", cls); - T t = (T) xstream.fromXML(xmlStr); - return t; - } -} diff --git a/epmet-module/epmet-third/epmet-third-server/src/main/resources/mapper/ComponentAccessTokenDao.xml b/epmet-module/epmet-third/epmet-third-server/src/main/resources/mapper/ComponentAccessTokenDao.xml index e3cda3bc0e..53e1820b8a 100644 --- a/epmet-module/epmet-third/epmet-third-server/src/main/resources/mapper/ComponentAccessTokenDao.xml +++ b/epmet-module/epmet-third/epmet-third-server/src/main/resources/mapper/ComponentAccessTokenDao.xml @@ -33,7 +33,7 @@ @@ -41,11 +41,10 @@ SELECT COUNT(*) FROM - refresh_authorizer_access_token + component_access_token WHERE del_flag = 0 AND (UNIX_TIMESTAMP(expires_in_time) - UNIX_TIMESTAMP(NOW())) 900 - AND (UNIX_TIMESTAMP(expires_in_time) - UNIX_TIMESTAMP(NOW())) > 0 \ No newline at end of file