Эх сурвалжийг харах

企业回调调整XML解析方式

zhouy 11 сар өмнө
parent
commit
e10b4319f6

+ 13 - 13
qywx-sdk/src/main/java/com/usoftchina/qywx/sdk/weixin/AesException.java

@@ -1,20 +1,20 @@
-package com.usoftchina.qywx.sdk.weixin;
+package com.usoftchina.qywx.sdk.weixin;
 
 @SuppressWarnings("serial")
 public class AesException extends Exception {
 
 	public final static int OK = 0;
 	public final static int ValidateSignatureError = -40001;
-	public final static int ParseJsonError = -40002;
+	public final static int ParseXmlError = -40002;
 	public final static int ComputeSignatureError = -40003;
 	public final static int IllegalAesKey = -40004;
 	public final static int ValidateCorpidError = -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 GenReturnJsonError = -40011;
+	//public final static int EncodeBase64Error = -40009;
+	//public final static int DecodeBase64Error = -40010;
+	//public final static int GenReturnXmlError = -40011;
 
 	private int code;
 
@@ -22,8 +22,8 @@ public class AesException extends Exception {
 		switch (code) {
 		case ValidateSignatureError:
 			return "签名验证错误";
-		case ParseJsonError:
-			return "json解析失败";
+		case ParseXmlError:
+			return "xml解析失败";
 		case ComputeSignatureError:
 			return "sha加密生成签名失败";
 		case IllegalAesKey:
@@ -36,12 +36,12 @@ public class AesException extends Exception {
 			return "aes解密失败";
 		case IllegalBuffer:
 			return "解密后得到的buffer非法";
-		case EncodeBase64Error:
-			return "base64加密错误";
-		case DecodeBase64Error:
-			return "base64解密错误";
-		case GenReturnJsonError:
-			return "josn生成失败";
+//		case EncodeBase64Error:
+//			return "base64加密错误";
+//		case DecodeBase64Error:
+//			return "base64解密错误";
+//		case GenReturnXmlError:
+//			return "xml生成失败";
 		default:
 			return null; // cannot be
 		}

+ 0 - 54
qywx-sdk/src/main/java/com/usoftchina/qywx/sdk/weixin/JsonParse.java

@@ -1,54 +0,0 @@
-
-package com.usoftchina.qywx.sdk.weixin;
-
-
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONObject;
-
-/**
- * JsonParse class
- *
- * 提供提取消息格式中的密文及生成回复消息格式的接口.
- */
-public class JsonParse {
-
-	/**
-	 * 提取出 JSON 包中的加密消息
-	 * @param jsontext 待提取的json字符串
-	 * @return 提取出的加密消息字符串
-	 * @throws AesException
-	 */
-	public static Object[] extract(String jsontext) throws AesException     {
-		Object[] result = new Object[3];
-		try {
-
-			JSONObject json = JSON.parseObject(jsontext);
-        	String encrypt_msg = json.getString("encrypt");
-			String tousername  = json.getString("tousername");
-			String agentid     = json.getString("agentid");
-
-			result[0] = tousername;
-			result[1] = encrypt_msg;
-			result[2] = agentid;
-			return result;
-		} catch (Exception e) {
-			e.printStackTrace();
-			throw new AesException(AesException.ParseJsonError);
-		}
-	}
-
-	/**
-	 * 生成json消息
-	 * @param encrypt 加密后的消息密文
-	 * @param signature 安全签名
-	 * @param timestamp 时间戳
-	 * @param nonce 随机字符串
-	 * @return 生成的json字符串
-	 */
-	public static String generate(String encrypt, String signature, String timestamp, String nonce) {
-
-		String format = "{\"encrypt\":\"%1$s\",\"msgsignature\":\"%2$s\",\"timestamp\":\"%3$s\",\"nonce\":\"%4$s\"}";
-		return String.format(format, encrypt, signature, timestamp, nonce);
-
-	}
-}

+ 54 - 25
qywx-sdk/src/main/java/com/usoftchina/qywx/sdk/weixin/WXBizJsonMsgCrypt.java → qywx-sdk/src/main/java/com/usoftchina/qywx/sdk/weixin/WXBizMsgCrypt.java

@@ -1,24 +1,52 @@
+/**
+ * 对企业微信发送给企业后台的消息加解密示例代码.
+ * 
+ * @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.usoftchina.qywx.sdk.weixin;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-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.Base64;
 import java.util.Random;
 
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
 
-public class WXBizJsonMsgCrypt {
-	private static Charset CHARSET = Charset.forName("utf-8");;
-	private byte[] aesKey;
-	private String token;
-	private String receiveid;
-	private Logger logger = LoggerFactory.getLogger(getClass());
+import java.util.Base64;
 
+/**
+ * 提供接收和推送给企业微信消息的加解密接口(UTF8编码的字符串).
+ * <ol>
+ * 	<li>第三方回复加密消息给企业微信</li>
+ * 	<li>第三方收到企业微信发送的消息,验证消息的安全性,并对消息进行解密。</li>
+ * </ol>
+ * 说明:异常java.security.InvalidKeyException:illegal Key Size的解决方案
+ * <ol>
+ * 	<li>在官方网站下载JCE无限制权限策略文件(JDK7的下载地址:
+ *      http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html</li>
+ * 	<li>下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt</li>
+ * 	<li>如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件</li>
+ * 	<li>如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件</li>
+ * </ol>
+ */
+public class WXBizMsgCrypt {
+	static Charset CHARSET = Charset.forName("utf-8");
+	byte[] aesKey;
+	String token;
+	String receiveid;
+	private Logger logger = LoggerFactory.getLogger(getClass());
 	/**
 	 * 构造函数
 	 * @param token 企业微信后台,开发者设置的token
@@ -27,7 +55,7 @@ public class WXBizJsonMsgCrypt {
 	 * 
 	 * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
 	 */
-	public WXBizJsonMsgCrypt(String token, String encodingAesKey, String receiveid) throws AesException {
+	public WXBizMsgCrypt(String token, String encodingAesKey, String receiveid) throws AesException {
 		if (encodingAesKey.length() != 43) {
 			throw new AesException(AesException.IllegalAesKey);
 		}
@@ -107,7 +135,7 @@ public class WXBizJsonMsgCrypt {
 			byte[] encrypted = cipher.doFinal(unencrypted);
 
 			// 使用BASE64对加密后的字符串进行编码
-			String base64Encrypted =Base64.getEncoder().encodeToString(encrypted);
+			String base64Encrypted = Base64.getEncoder().encodeToString(encrypted);
 
 			return base64Encrypted;
 		} catch (Exception e) {
@@ -142,7 +170,7 @@ public class WXBizJsonMsgCrypt {
 			throw new AesException(AesException.DecryptAESError);
 		}
 
-		String jsonContent, from_receiveid;
+		String xmlContent, from_receiveid;
 		try {
 			// 去除补位字符
 			byte[] bytes = PKCS7Encoder.decode(original);
@@ -150,22 +178,21 @@ public class WXBizJsonMsgCrypt {
 			// 分离16位随机字符串,网络字节序和receiveid
 			byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);
 
-			int jsonLength = recoverNetworkBytesOrder(networkOrder);
+			int xmlLength = recoverNetworkBytesOrder(networkOrder);
 
-			jsonContent = new String(Arrays.copyOfRange(bytes, 20, 20 + jsonLength), CHARSET);
-			from_receiveid = new String(Arrays.copyOfRange(bytes, 20 + jsonLength, bytes.length),
+			xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET);
+			from_receiveid = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length),
 					CHARSET);
 		} catch (Exception e) {
 			e.printStackTrace();
 			throw new AesException(AesException.IllegalBuffer);
 		}
-
-		// receiveid不相同的情况
 		logger.info("Qywx cropid Check from_receiveid{} ,receiveid{}" , from_receiveid , receiveid  );
+		// receiveid不相同的情况
 		if (!from_receiveid.equals(receiveid)) {
 			throw new AesException(AesException.ValidateCorpidError);
 		}
-		return jsonContent;
+		return xmlContent;
 
 	}
 
@@ -174,14 +201,14 @@ public class WXBizJsonMsgCrypt {
 	 * <ol>
 	 * 	<li>对要发送的消息进行AES-CBC加密</li>
 	 * 	<li>生成安全签名</li>
-	 * 	<li>将消息密文和安全签名打包成json格式</li>
+	 * 	<li>将消息密文和安全签名打包成xml格式</li>
 	 * </ol>
 	 * 
-	 * @param replyMsg 企业微信待回复用户的消息,json格式的字符串
+	 * @param replyMsg 企业微信待回复用户的消息,xml格式的字符串
 	 * @param timeStamp 时间戳,可以自己生成,也可以用URL参数的timestamp
 	 * @param nonce 随机串,可以自己生成,也可以用URL参数的nonce
 	 * 
-	 * @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的json格式的字符串
+	 * @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串
 	 * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
 	 */
 	public String EncryptMsg(String replyMsg, String timeStamp, String nonce) throws AesException {
@@ -195,7 +222,9 @@ public class WXBizJsonMsgCrypt {
 
 		String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt);
 
-		String result = JsonParse.generate(encrypt, signature, timeStamp, nonce);
+		// System.out.println("发送给平台的签名是: " + signature[1].toString());
+		// 生成发送的xml
+		String result = XMLParse.generate(encrypt, signature, timeStamp, nonce);
 		return result;
 	}
 
@@ -203,7 +232,7 @@ public class WXBizJsonMsgCrypt {
 	 * 检验消息的真实性,并且获取解密后的明文.
 	 * <ol>
 	 * 	<li>利用收到的密文生成安全签名,进行签名验证</li>
-	 * 	<li>若验证通过,则提取json中的加密消息</li>
+	 * 	<li>若验证通过,则提取xml中的加密消息</li>
 	 * 	<li>对消息进行解密</li>
 	 * </ol>
 	 * 
@@ -220,7 +249,7 @@ public class WXBizJsonMsgCrypt {
 
 		// 密钥,公众账号的app secret
 		// 提取密文
-		Object[] encrypt = JsonParse.extract(postData);
+		Object[] encrypt = XMLParse.extract(postData);
 
 		// 验证安全签名
 		String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt[1].toString());

+ 104 - 0
qywx-sdk/src/main/java/com/usoftchina/qywx/sdk/weixin/XMLParse.java

@@ -0,0 +1,104 @@
+/**
+ * 对企业微信发送给企业后台的消息加解密示例代码.
+ * 
+ * @copyright Copyright (c) 1998-2014 Tencent Inc.
+ */
+
+// ------------------------------------------------------------------------
+
+package com.usoftchina.qywx.sdk.weixin;
+
+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();
+			
+			String FEATURE = null;
+			// This is the PRIMARY defense. If DTDs (doctypes) are disallowed, almost all XML entity attacks are prevented
+			// Xerces 2 only - http://xerces.apache.org/xerces2-j/features.html#disallow-doctype-decl
+			FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
+			dbf.setFeature(FEATURE, true);
+			
+			// If you can't completely disable DTDs, then at least do the following:
+			// Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities
+			// Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities
+			// JDK7+ - http://xml.org/sax/features/external-general-entities 
+			FEATURE = "http://xml.org/sax/features/external-general-entities";
+			dbf.setFeature(FEATURE, false);
+			
+			// Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-parameter-entities
+			// Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities
+			// JDK7+ - http://xml.org/sax/features/external-parameter-entities 
+			FEATURE = "http://xml.org/sax/features/external-parameter-entities";
+			dbf.setFeature(FEATURE, false);
+			
+			// Disable external DTDs as well
+			FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
+			dbf.setFeature(FEATURE, false);
+			
+			// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
+			dbf.setXIncludeAware(false);
+			dbf.setExpandEntityReferences(false);
+			
+			// And, per Timothy Morgan: "If for some reason support for inline DOCTYPEs are a requirement, then 
+			// ensure the entity settings are disabled (as shown above) and beware that SSRF attacks
+			// (http://cwe.mitre.org/data/definitions/918.html) and denial 
+			// of service attacks (such as billion laughs or decompression bombs via "jar:") are a risk."
+			
+			// remaining parser logic
+			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");
+			result[0] = 0;
+			result[1] = nodelist1.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 = "<xml>\n" + "<Encrypt><![CDATA[%1$s]]></Encrypt>\n"
+				+ "<MsgSignature><![CDATA[%2$s]]></MsgSignature>\n"
+				+ "<TimeStamp>%3$s</TimeStamp>\n" + "<Nonce><![CDATA[%4$s]]></Nonce>\n" + "</xml>";
+		return String.format(format, encrypt, signature, timestamp, nonce);
+
+	}
+}

+ 5 - 9
uas-office-qywx/src/main/java/com/usoftchina/uas/office/qywx/manage/service/QywxCallbackService.java

@@ -3,12 +3,10 @@ package com.usoftchina.uas.office.qywx.manage.service;
 import com.alibaba.fastjson.JSON;
 import com.fasterxml.jackson.dataformat.xml.XmlMapper;
 import com.usoftchina.qywx.sdk.DevSDK;
-import com.usoftchina.qywx.sdk.ScheduleSdk;
 import com.usoftchina.qywx.sdk.dto.GetProviderTokenResp;
 import com.usoftchina.qywx.sdk.util.QywxConst;
 import com.usoftchina.qywx.sdk.weixin.AesException;
-import com.usoftchina.qywx.sdk.weixin.WXBizJsonMsgCrypt;
-import com.usoftchina.uas.office.dto.Result;
+import com.usoftchina.qywx.sdk.weixin.WXBizMsgCrypt;
 import com.usoftchina.uas.office.entity.DataCenter;
 import com.usoftchina.uas.office.jdbc.DataSourceHolder;
 import com.usoftchina.uas.office.qywx.config.ProviderConfig;
@@ -21,7 +19,6 @@ import org.springframework.context.annotation.Lazy;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 import org.springframework.util.ObjectUtils;
-import org.springframework.util.StringUtils;
 
 import java.io.IOException;
 import java.util.concurrent.TimeUnit;
@@ -49,7 +46,7 @@ public class QywxCallbackService extends AbstractService {
         String callBackStr = null;
         try {
             logger.info("企业微信MsgCrypt sToken {} sEncodingAESKey{} sCorpID{}", providerConfig.getToken() , providerConfig.getEncodingAESKey(), getCropId());
-            WXBizJsonMsgCrypt wxcpt = new WXBizJsonMsgCrypt(providerConfig.getToken(), providerConfig.getEncodingAESKey() , getCropId());
+            WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(providerConfig.getToken(), providerConfig.getEncodingAESKey() , getCropId());
             callBackStr = wxcpt.VerifyURL(msgSignature, String.valueOf(timestamp),
                     nonce, echostr);
         } catch (AesException e) {
@@ -70,13 +67,12 @@ public class QywxCallbackService extends AbstractService {
         } catch (IOException e) {
             e.printStackTrace();
         }
-        logger.info("企业微信CallBack POST ");
+        logger.info("企业微信CallBack:  msgSignature{} , timestamp {},nonce {} ", msgSignature ,timestamp ,nonce);
         try {
-            WXBizJsonMsgCrypt wxcpt = new WXBizJsonMsgCrypt(providerConfig.getToken(), providerConfig.getEncodingAESKey(), getCropId());
+            WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(providerConfig.getToken(), providerConfig.getEncodingAESKey(), getCropId());
 
-            decryptMsg = wxcpt.DecryptMsg(msgSignature, String.valueOf(timestamp), nonce, qywxCallback.getEncrypt());
+            decryptMsg = wxcpt.DecryptMsg(msgSignature, String.valueOf(timestamp), nonce, messageXml);
             logger.info("企业微信CallBack POST {}", decryptMsg);
-            JSON.parseObject(decryptMsg);
 
         } catch (AesException e) {
             e.printStackTrace();