From c3924471faab0e9a65cc9082dfda3364162f564f Mon Sep 17 00:00:00 2001 From: zeek <984294471@qq.com> Date: Wed, 18 Mar 2020 22:35:54 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E6=95=99=E7=A0=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/qq/weixin/mp/aes/AesException.java | 2 +- .../java/com/qq/weixin/mp/aes/ByteGroup.java | 26 -- .../com/qq/weixin/mp/aes/PKCS7Encoder.java | 67 ---- src/main/java/com/qq/weixin/mp/aes/SHA1.java | 61 ---- .../com/qq/weixin/mp/aes/WXBizMsgCrypt.java | 289 ------------------ .../java/com/qq/weixin/mp/aes/XMLParse.java | 78 ----- .../solo/weixin/controller/Program.java | 72 ----- .../weixin/controller/WeXinController.java | 25 +- .../com/zeekling/solo/weixin/verify/SHA1.java | 54 ++++ src/main/resources/application-dev.properties | 4 +- .../qq/weixin/mp/aes/WXBizMsgCryptTest.java | 152 --------- 11 files changed, 69 insertions(+), 761 deletions(-) delete mode 100644 src/main/java/com/qq/weixin/mp/aes/ByteGroup.java delete mode 100644 src/main/java/com/qq/weixin/mp/aes/PKCS7Encoder.java delete mode 100644 src/main/java/com/qq/weixin/mp/aes/SHA1.java delete mode 100644 src/main/java/com/qq/weixin/mp/aes/WXBizMsgCrypt.java delete mode 100644 src/main/java/com/qq/weixin/mp/aes/XMLParse.java delete mode 100644 src/main/java/com/zeekling/solo/weixin/controller/Program.java create mode 100644 src/main/java/com/zeekling/solo/weixin/verify/SHA1.java delete mode 100644 src/test/java/com/qq/weixin/mp/aes/WXBizMsgCryptTest.java diff --git a/src/main/java/com/qq/weixin/mp/aes/AesException.java b/src/main/java/com/qq/weixin/mp/aes/AesException.java index d2a489e..1722b49 100644 --- a/src/main/java/com/qq/weixin/mp/aes/AesException.java +++ b/src/main/java/com/qq/weixin/mp/aes/AesException.java @@ -51,7 +51,7 @@ public class AesException extends Exception { return code; } - AesException(int code) { + public AesException(int code) { super(getMessage(code)); this.code = code; } diff --git a/src/main/java/com/qq/weixin/mp/aes/ByteGroup.java b/src/main/java/com/qq/weixin/mp/aes/ByteGroup.java deleted file mode 100644 index f773e3b..0000000 --- a/src/main/java/com/qq/weixin/mp/aes/ByteGroup.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.qq.weixin.mp.aes; - -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/src/main/java/com/qq/weixin/mp/aes/PKCS7Encoder.java b/src/main/java/com/qq/weixin/mp/aes/PKCS7Encoder.java deleted file mode 100644 index b402ab0..0000000 --- a/src/main/java/com/qq/weixin/mp/aes/PKCS7Encoder.java +++ /dev/null @@ -1,67 +0,0 @@ -/** - * 对公众平台发送给公众账号的消息加解密示例代码. - * - * @copyright Copyright (c) 1998-2014 Tencent Inc. - */ - -// ------------------------------------------------------------------------ - -package com.qq.weixin.mp.aes; - -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/src/main/java/com/qq/weixin/mp/aes/SHA1.java b/src/main/java/com/qq/weixin/mp/aes/SHA1.java deleted file mode 100644 index e28599a..0000000 --- a/src/main/java/com/qq/weixin/mp/aes/SHA1.java +++ /dev/null @@ -1,61 +0,0 @@ -/** - * 对公众平台发送给公众账号的消息加解密示例代码. - * - * @copyright Copyright (c) 1998-2014 Tencent Inc. - */ - -// ------------------------------------------------------------------------ - -package com.qq.weixin.mp.aes; - -import java.security.MessageDigest; -import java.util.Arrays; - -/** - * SHA1 class - * - * 计算公众平台的消息签名接口. - */ -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/src/main/java/com/qq/weixin/mp/aes/WXBizMsgCrypt.java b/src/main/java/com/qq/weixin/mp/aes/WXBizMsgCrypt.java deleted file mode 100644 index a1f1d95..0000000 --- a/src/main/java/com/qq/weixin/mp/aes/WXBizMsgCrypt.java +++ /dev/null @@ -1,289 +0,0 @@ -/** - * 对公众平台发送给公众账号的消息加解密示例代码. - * - * @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.qq.weixin.mp.aes; - -import java.nio.charset.Charset; -import java.util.Arrays; -import java.util.Random; - -import javax.crypto.Cipher; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; - -import org.apache.commons.codec.binary.Base64; - -/** - * 提供接收和推送给公众平台消息的加解密接口(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.equals("")) { - 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 { - - // 密钥,公众账号的app secret - // 提取密文 - Object[] encrypt = XMLParse.extract(postData); - - // 验证安全签名 - String signature = SHA1.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; - } - - /** - * 验证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/src/main/java/com/qq/weixin/mp/aes/XMLParse.java b/src/main/java/com/qq/weixin/mp/aes/XMLParse.java deleted file mode 100644 index 8becf81..0000000 --- a/src/main/java/com/qq/weixin/mp/aes/XMLParse.java +++ /dev/null @@ -1,78 +0,0 @@ -/** - * 对公众平台发送给公众账号的消息加解密示例代码. - * - * @copyright Copyright (c) 1998-2014 Tencent Inc. - */ - -// ------------------------------------------------------------------------ - -package com.qq.weixin.mp.aes; - -import java.io.StringReader; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; -import org.xml.sax.InputSource; - -/** - * XMLParse class - * - * 提供提取消息格式中的密文及生成回复消息格式的接口. - */ -class XMLParse { - - /** - * 提取出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(); - 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/src/main/java/com/zeekling/solo/weixin/controller/Program.java b/src/main/java/com/zeekling/solo/weixin/controller/Program.java deleted file mode 100644 index e887964..0000000 --- a/src/main/java/com/zeekling/solo/weixin/controller/Program.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.zeekling.solo.weixin.controller; - -import com.qq.weixin.mp.aes.WXBizMsgCrypt; -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; - -/** - * @author zeekling [lingzhaohui@zeekling.cn] - * @version 1.0 - * @apiNote - * @since 2020-03-18 - */ -public class Program { - - public static void test(String[] args) throws Exception { - - // - // 第三方回复公众平台 - // - - // 需要加密的明文 - String encodingAesKey = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG"; - String token = "pamtest"; - String timestamp = "1409304348"; - String nonce = "xxxxxx"; - String appId = "wxb11529c136998cb6"; - String replyMsg = " 中文1407743423"; - - WXBizMsgCrypt pc = new WXBizMsgCrypt(token, encodingAesKey, appId); - String mingwen = pc.encryptMsg(replyMsg, timestamp, nonce); - System.out.println("加密后: " + mingwen); - - 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(mingwen); - InputSource is = new InputSource(sr); - Document document = db.parse(is); - - Element root = document.getDocumentElement(); - NodeList nodelist1 = root.getElementsByTagName("Encrypt"); - NodeList nodelist2 = root.getElementsByTagName("MsgSignature"); - - String encrypt = nodelist1.item(0).getTextContent(); - String msgSignature = nodelist2.item(0).getTextContent(); - - String format = ""; - String fromXML = String.format(format, encrypt); - - // - // 公众平台发送消息给第三方,第三方处理 - // - - // 第三方收到公众号平台发送的消息 - String result2 = pc.decryptMsg(msgSignature, timestamp, nonce, fromXML); - System.out.println("解密后明文: " + result2); - - //pc.verifyUrl(null, null, null, null); - } -} diff --git a/src/main/java/com/zeekling/solo/weixin/controller/WeXinController.java b/src/main/java/com/zeekling/solo/weixin/controller/WeXinController.java index e35132e..42bbeea 100644 --- a/src/main/java/com/zeekling/solo/weixin/controller/WeXinController.java +++ b/src/main/java/com/zeekling/solo/weixin/controller/WeXinController.java @@ -1,19 +1,15 @@ package com.zeekling.solo.weixin.controller; import com.qq.weixin.mp.aes.AesException; -import com.qq.weixin.mp.aes.WXBizMsgCrypt; import com.zeekling.solo.weixin.conf.WeXinConfigure; import com.zeekling.solo.weixin.entity.Validate; -import me.chanjar.weixin.mp.api.WxMpService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; +import com.zeekling.solo.weixin.verify.SHA1; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.Arrays; /** * @author zeekling [lingzhaohui@zeekling.cn] @@ -24,6 +20,7 @@ import java.util.Arrays; @RestController public class WeXinController { + private Logger logger = LoggerFactory.getLogger(getClass()); @Resource private WeXinConfigure wechatConfig; @@ -31,16 +28,18 @@ public class WeXinController { @RequestMapping(value = "/validate") public String validate(Validate validate) throws AesException { String token = wechatConfig.getToken(); - WXBizMsgCrypt pc = new WXBizMsgCrypt(token, wechatConfig.getAesKey(), wechatConfig.getAppId()); - String signature = pc.encryptMsg(validate.getEchostr(), validate.getTimestamp(), validate.getNonce()); - if(!"".equals(signature) && !"".equals(validate.getSignature()) && signature.equals(validate.getSignature())){ - System.out.println("-----签名校验通过-----"); + String signature = SHA1.getSHA1(token, validate.getTimestamp(), validate.getNonce()); + // 3.字符串校验 + if (validate.getSignature().equals(signature)) { + logger.info("微信-签名校验通过"); + logger.info("回复给微信的 echostr 字符串:{}", validate.getEchostr()); return validate.getEchostr(); - }else { - System.out.println("-----校验签名失败-----"); + } else { + logger.error("微信-签名校验失败"); return ""; } } + } diff --git a/src/main/java/com/zeekling/solo/weixin/verify/SHA1.java b/src/main/java/com/zeekling/solo/weixin/verify/SHA1.java new file mode 100644 index 0000000..f3c88de --- /dev/null +++ b/src/main/java/com/zeekling/solo/weixin/verify/SHA1.java @@ -0,0 +1,54 @@ +package com.zeekling.solo.weixin.verify; + +import com.qq.weixin.mp.aes.AesException; + +import java.security.MessageDigest; +import java.util.Arrays; + +/** + * @author zeekling [lingzhaohui@zeekling.cn] + * @version 1.0 + * @apiNote + * @since 2020-03-18 + */ +public class SHA1 { + + /** + * 用SHA1算法验证Token + * + * @param token 票据 + * @param timestamp 时间戳 + * @param nonce 随机字符串 + * @return 安全签名 + */ + public static String getSHA1(String token, String timestamp, String nonce) throws AesException { + try { + String[] array = new String[]{token, timestamp, nonce}; + StringBuilder sb = new StringBuilder(); + // 字符串排序 + Arrays.sort(array); + for (int i = 0; i < 3; i++) { + sb.append(array[i]); + } + String str = sb.toString(); + // SHA1签名生成 + MessageDigest md = MessageDigest.getInstance("SHA-1"); + md.update(str.getBytes()); + byte[] digest = md.digest(); + + StringBuilder hexstr = new StringBuilder(); + String shaHex = ""; + for (byte aDigest : digest) { + shaHex = Integer.toHexString(aDigest & 0xFF); + if (shaHex.length() < 2) { + hexstr.append(0); + } + hexstr.append(shaHex); + } + return hexstr.toString(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 201b9a8..274622a 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -2,7 +2,7 @@ server.port=9090 logging.file=/home/zeek/project/solo-weixin/logs/weixin.log # 公众号配置(必填) -wx.mp.appId=@appId +wx.mp.appId=zeekling wx.mp.secret=@secret -wx.mp.token=@token +wx.mp.token=zeekling wx.mp.aesKey=@aesKey \ No newline at end of file diff --git a/src/test/java/com/qq/weixin/mp/aes/WXBizMsgCryptTest.java b/src/test/java/com/qq/weixin/mp/aes/WXBizMsgCryptTest.java deleted file mode 100644 index 55e73ab..0000000 --- a/src/test/java/com/qq/weixin/mp/aes/WXBizMsgCryptTest.java +++ /dev/null @@ -1,152 +0,0 @@ -package com.qq.weixin.mp.aes; - -import static org.junit.Assert.*; - -import java.io.IOException; -import java.io.StringReader; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; - -public class WXBizMsgCryptTest { - String encodingAesKey = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG"; - String token = "pamtest"; - String timestamp = "1409304348"; - String nonce = "xxxxxx"; - String appId = "wxb11529c136998cb6"; - String replyMsg = "我是中文abcd123"; - String xmlFormat = ""; - String afterAesEncrypt = "jn1L23DB+6ELqJ+6bruv21Y6MD7KeIfP82D6gU39rmkgczbWwt5+3bnyg5K55bgVtVzd832WzZGMhkP72vVOfg=="; - String randomStr = "aaaabbbbccccdddd"; - - String replyMsg2 = "1407743423"; - String afterAesEncrypt2 = "jn1L23DB+6ELqJ+6bruv23M2GmYfkv0xBh2h+XTBOKVKcgDFHle6gqcZ1cZrk3e1qjPQ1F4RsLWzQRG9udbKWesxlkupqcEcW7ZQweImX9+wLMa0GaUzpkycA8+IamDBxn5loLgZpnS7fVAbExOkK5DYHBmv5tptA9tklE/fTIILHR8HLXa5nQvFb3tYPKAlHF3rtTeayNf0QuM+UW/wM9enGIDIJHF7CLHiDNAYxr+r+OrJCmPQyTy8cVWlu9iSvOHPT/77bZqJucQHQ04sq7KZI27OcqpQNSto2OdHCoTccjggX5Z9Mma0nMJBU+jLKJ38YB1fBIz+vBzsYjrTmFQ44YfeEuZ+xRTQwr92vhA9OxchWVINGC50qE/6lmkwWTwGX9wtQpsJKhP+oS7rvTY8+VdzETdfakjkwQ5/Xka042OlUb1/slTwo4RscuQ+RdxSGvDahxAJ6+EAjLt9d8igHngxIbf6YyqqROxuxqIeIch3CssH/LqRs+iAcILvApYZckqmA7FNERspKA5f8GoJ9sv8xmGvZ9Yrf57cExWtnX8aCMMaBropU/1k+hKP5LVdzbWCG0hGwx/dQudYR/eXp3P0XxjlFiy+9DMlaFExWUZQDajPkdPrEeOwofJb"; - - @BeforeClass - public static void setUpBeforeClass() throws Exception { - } - - @AfterClass - public static void tearDownAfterClass() throws Exception { - } - - @Before - public void setUp() throws Exception { - - } - - @After - public void tearDown() throws Exception { - } - - @Test - public void testNormal() throws ParserConfigurationException, SAXException, IOException { - try { - WXBizMsgCrypt pc = new WXBizMsgCrypt(token, encodingAesKey, appId); - String afterEncrpt = pc.encryptMsg(replyMsg, timestamp, nonce); - - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - DocumentBuilder db = dbf.newDocumentBuilder(); - StringReader sr = new StringReader(afterEncrpt); - InputSource is = new InputSource(sr); - Document document = db.parse(is); - - Element root = document.getDocumentElement(); - NodeList nodelist1 = root.getElementsByTagName("Encrypt"); - NodeList nodelist2 = root.getElementsByTagName("MsgSignature"); - - String encrypt = nodelist1.item(0).getTextContent(); - String msgSignature = nodelist2.item(0).getTextContent(); - String fromXML = String.format(xmlFormat, encrypt); - - // 第三方收到公众号平台发送的消息 - String afterDecrpt = pc.decryptMsg(msgSignature, timestamp, nonce, fromXML); - assertEquals(replyMsg, afterDecrpt); - } catch (AesException e) { - fail("正常流程,怎么就抛出异常了??????"); - } - } - - @Test - public void testAesEncrypt() { - try { - WXBizMsgCrypt pc = new WXBizMsgCrypt(token, encodingAesKey, appId); - assertEquals(afterAesEncrypt, pc.encrypt(randomStr, replyMsg)); - } catch (AesException e) { - e.printStackTrace(); - fail("no异常"); - } - } - - @Test - public void testAesEncrypt2() { - try { - WXBizMsgCrypt pc = new WXBizMsgCrypt(token, encodingAesKey, appId); - assertEquals(afterAesEncrypt2, pc.encrypt(randomStr, replyMsg2)); - - } catch (AesException e) { - e.printStackTrace(); - fail("no异常"); - } - } - - @Test - public void testIllegalAesKey() { - try { - new WXBizMsgCrypt(token, "abcde", appId); - } catch (AesException e) { - assertEquals(AesException.IllegalAesKey, e.getCode()); - return; - } - fail("错误流程不抛出异常???"); - } - - @Test - public void testValidateSignatureError() throws ParserConfigurationException, SAXException, - IOException { - try { - WXBizMsgCrypt pc = new WXBizMsgCrypt(token, encodingAesKey, appId); - String afterEncrpt = pc.encryptMsg(replyMsg, timestamp, nonce); - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - DocumentBuilder db = dbf.newDocumentBuilder(); - StringReader sr = new StringReader(afterEncrpt); - InputSource is = new InputSource(sr); - Document document = db.parse(is); - - Element root = document.getDocumentElement(); - NodeList nodelist1 = root.getElementsByTagName("Encrypt"); - - String encrypt = nodelist1.item(0).getTextContent(); - String fromXML = String.format(xmlFormat, encrypt); - pc.decryptMsg("12345", timestamp, nonce, fromXML); // 这里签名错误 - } catch (AesException e) { - assertEquals(AesException.ValidateSignatureError, e.getCode()); - return; - } - fail("错误流程不抛出异常???"); - } - - @Test - public void testVerifyUrl() throws AesException { - WXBizMsgCrypt wxcpt = new WXBizMsgCrypt("QDG6eK", - "jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C", "wx5823bf96d3bd56c7"); - String verifyMsgSig = "5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3"; - String timeStamp = "1409659589"; - String nonce = "263014780"; - String echoStr = "P9nAzCzyDtyTWESHep1vC5X9xho/qYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp+4RPcs8TgAE7OaBO+FZXvnaqQ=="; - wxcpt.verifyUrl(verifyMsgSig, timeStamp, nonce, echoStr); - // 只要不抛出异常就好 - } -}