From cd8f73819900dd41e1a92b0393e67da57684f9a3 Mon Sep 17 00:00:00 2001 From: cuilei <ray_tsu1@163.com> Date: 星期三, 02 七月 2025 19:27:01 +0800 Subject: [PATCH] 企业微信模板卡片消息回调接口、模板卡片消息参数实体封装 --- lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/AesException.java | 59 +++ lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/vo/TemplateCard.java | 20 + lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/SHA1.java | 61 +++ lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/Sample.java | 136 ++++++++ lxzn-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java | 2 lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/PKCS7Encoder.java | 67 +++ lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/MessageAPI.java | 26 + lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/controller/WeComCallbackController.java | 136 ++++++++ lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/XMLParse.java | 104 ++++++ lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/vo/TemplateCardEntity.java | 94 +++++ lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/ByteGroup.java | 26 + lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/WXBizMsgCrypt.java | 289 +++++++++++++++++ 12 files changed, 1,020 insertions(+), 0 deletions(-) diff --git a/lxzn-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java b/lxzn-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java index 982587e..307b51d 100644 --- a/lxzn-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java +++ b/lxzn-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroConfig.java @@ -163,6 +163,8 @@ //娴嬭瘯妯″潡鎺掗櫎 filterChainDefinitionMap.put("/test/seata/**", "anon"); + //浼佷笟寰俊娑堟伅鍥炶皟鎺ュ彛 + filterChainDefinitionMap.put("/qywx/message/callback/**", "anon"); // 娣诲姞鑷繁鐨勮繃婊ゅ櫒骞朵笖鍙栧悕涓簀wt diff --git a/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/MessageAPI.java b/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/MessageAPI.java new file mode 100644 index 0000000..3cda9bf --- /dev/null +++ b/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/MessageAPI.java @@ -0,0 +1,26 @@ +package org.jeecg.modules.qywx.message; + +import com.alibaba.fastjson.JSONObject; +import com.jeecg.qywx.api.core.util.HttpUtil; +import org.jeecg.modules.qywx.message.vo.TemplateCard; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MessageAPI { + private static final Logger logger = LoggerFactory.getLogger(MessageAPI.class); + //鍙戦�佹秷鎭紙post锛� + static String message_send_url="https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=ACCESS_TOKEN"; + + public static JSONObject sendTemplateCardMessage(TemplateCard templateCard, String accessToken) { + logger.info("[MessageAPI] sendTemplateCardMessage params:accessToken:{},templateCard:{}", new Object[]{accessToken, templateCard}); + // 鎷艰鍙戦�佷俊鎭殑url + String url = message_send_url.replace("ACCESS_TOKEN", accessToken); + // 灏嗕俊鎭璞¤浆鎹㈡垚json瀛楃涓� + String params = JSONObject.toJSONString(templateCard); + logger.info("[MessageAPI] sendTemplateCardMessage params:jsonText:{}", new Object[]{params}); + // 璋冪敤鎺ュ彛鍙戦�佷俊鎭� + JSONObject jsonObject = HttpUtil.sendPost(url, params); + logger.info("[MessageAPI] sendTemplateCardMessage response:{}", new Object[]{jsonObject.toJSONString()}); + return jsonObject; + } +} diff --git a/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/controller/WeComCallbackController.java b/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/controller/WeComCallbackController.java new file mode 100644 index 0000000..ad84126 --- /dev/null +++ b/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/controller/WeComCallbackController.java @@ -0,0 +1,136 @@ +package org.jeecg.modules.qywx.message.controller; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.digest.DigestUtils; +import org.jeecg.modules.qywx.message.utils.WXBizMsgCrypt; +import org.springframework.web.bind.annotation.*; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.StringReader; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.*; + +/** + * 浼佷笟寰俊娑堟伅鍥炶皟鎺ュ彛锛堟敮鎸� URL 楠岃瘉 + 娑堟伅鎺ユ敹锛� + */ +@RestController +@RequestMapping("/qywx/message/callback") +@Slf4j +public class WeComCallbackController { + + // 鏇挎崲涓轰綘鐨勫簲鐢ㄩ厤缃� + private static final String TOKEN = "bPGpQimYTtjAfAj2uTE4fb"; + private static final String AES_KEY = "hkbscaEtxWAydMIoWe8EW4drpHXaYroQPKBMoKCs2bf"; // EncodingAESKey + private static final String CORP_ID = "ww5999b2643c95fa75"; // 浼佷笟 ID 鎴� AppId + + private static WXBizMsgCrypt wxBizMsgCrypt; + + static { + try { + wxBizMsgCrypt = new WXBizMsgCrypt(TOKEN, AES_KEY, CORP_ID); + } catch (Exception e) { + throw new RuntimeException("鍒濆鍖� WXBizMsgCrypt 澶辫触锛岃妫�鏌� Token/AESKey/AppId 鏄惁姝g‘", e); + } + } + + /** + * GET 璇锋眰锛氱敤浜� URL 楠岃瘉锛圱oken 鏍¢獙锛� + */ + @GetMapping(produces = "text/plain;charset=utf-8") + public String verifyUrl( + @RequestParam("msg_signature") String msgSignature, + @RequestParam("timestamp") String timestamp, + @RequestParam("nonce") String nonce, + @RequestParam("echostr") String echostr) { + try { + log.info("鏀跺埌浼佷笟寰俊 URL 楠岃瘉璇锋眰锛歮sg_signature={}, timestamp={}, nonce={}, echostr={}", msgSignature, timestamp, nonce, echostr); + + // 璋冪敤瀹樻柟 SDK 楠岃瘉骞惰В瀵� + String decryptedEchoStr = wxBizMsgCrypt.VerifyURL(msgSignature, timestamp, nonce, echostr); + log.info("瑙e瘑鍚庣殑 echostr: {}", decryptedEchoStr); + + // 杩斿洖瑙e瘑鍚庣殑鍐呭锛堜紒涓氬井淇¤姹傚繀椤诲師鏍疯繑鍥烇級 + return decryptedEchoStr; + } catch (Exception e) { + log.error("URL 楠岃瘉澶辫触", e); + return "fail"; + } + } + + /** + * POST 璇锋眰锛氭帴鏀朵紒涓氬井淇℃帹閫佺殑娑堟伅 + */ + @PostMapping(produces = "text/xml;charset=utf-8") + public String handleWeComCallback(@RequestBody String xmlData, + @RequestParam("msg_signature") String msgSignature, + @RequestParam("timestamp") String timestamp, + @RequestParam("nonce") String nonce) { + try { + log.info("鏀跺埌浼佷笟寰俊娑堟伅鎺ㄩ�侊細msg_signature={}, timestamp={}, nonce={}", msgSignature, timestamp, nonce); + log.info("鍘熷 XML 鏁版嵁: {}", xmlData); + + // Step 1: 瑙e瘑娑堟伅 + String plainXml = wxBizMsgCrypt.DecryptMsg(msgSignature, timestamp, nonce, xmlData); + log.info("瑙e瘑鍚庣殑鏄庢枃娑堟伅: {}", plainXml); + + // Step 2: 瑙f瀽 XML 骞跺鐞嗕笟鍔¢�昏緫 + Map<String, String> msgMap = parseXmlToMap(plainXml); + String eventType = msgMap.get("Event"); + String content = msgMap.get("Content"); + + log.info("浜嬩欢绫诲瀷: {}, 娑堟伅鍐呭: {}", eventType, content); + + // Step 3: 涓氬姟澶勭悊锛堢ず渚嬶細鍥炲 success锛� + String responseXml = "<xml><returncode>0</returncode><returndata>success</returndata></xml>"; + return responseXml; + } catch (Exception e) { + log.error("澶勭悊浼佷笟寰俊娑堟伅澶辫触", e); + return "<xml><returncode>1</returncode><returndata>fail</returndata></xml>"; + } + } + + /** + * 灏� XML 杞负 Map + */ + private Map<String, String> parseXmlToMap(String xml) throws Exception { + Map<String, String> map = new HashMap<>(); + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + StringReader reader = new StringReader(xml); + InputSource source = new InputSource(reader); + Document doc = builder.parse(source); + Element root = doc.getDocumentElement(); + NodeList list = root.getChildNodes(); + + for (int i = 0; i < list.getLength(); i++) { + if (list.item(i).getNodeType() == Document.ELEMENT_NODE) { + Element element = (Element) list.item(i); + map.put(element.getNodeName(), element.getTextContent()); + } + } + return map; + } + + //public static void main(String[] args) throws UnsupportedEncodingException { + // String msg_signature = "eae930cbae5d988ed29f8aac6cf221016f81669e"; + // String timestamp = "1751356163"; + // String nonce = "16ljuxd4gd8"; + // String echostr = "Ax30h%2BharWss%2FeNNQl8x4KdzggrpmLFH8i%2F0mHqVGbcKcq1NeascovZV%2B08ooq5o0ng8RJ2WPyo69A8oGhKpxA%3D%3D"; + // + // String decode = URLDecoder.decode(echostr, "UTF-8"); + // //System.out.println(decode); + // WeComCallbackController weComCallbackController = new WeComCallbackController(); + // String decryptedEchoStr = weComCallbackController.verifyUrl(msg_signature, timestamp, nonce, decode); + // System.out.println(decryptedEchoStr); + //} +} diff --git a/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/AesException.java b/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/AesException.java new file mode 100644 index 0000000..9b1ecce --- /dev/null +++ b/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/AesException.java @@ -0,0 +1,59 @@ +package org.jeecg.modules.qywx.message.utils; + +@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 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 GenReturnXmlError = -40011; + + private int code; + + private static String getMessage(int code) { + switch (code) { + case ValidateSignatureError: + return "绛惧悕楠岃瘉閿欒"; + case ParseXmlError: + return "xml瑙f瀽澶辫触"; + case ComputeSignatureError: + return "sha鍔犲瘑鐢熸垚绛惧悕澶辫触"; + case IllegalAesKey: + return "SymmetricKey闈炴硶"; + case ValidateCorpidError: + return "corpid鏍¢獙澶辫触"; + case EncryptAESError: + return "aes鍔犲瘑澶辫触"; + case DecryptAESError: + return "aes瑙e瘑澶辫触"; + case IllegalBuffer: + return "瑙e瘑鍚庡緱鍒扮殑buffer闈炴硶"; +// case EncodeBase64Error: +// return "base64鍔犲瘑閿欒"; +// case DecodeBase64Error: +// return "base64瑙e瘑閿欒"; +// 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/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/ByteGroup.java b/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/ByteGroup.java new file mode 100644 index 0000000..96d94ab --- /dev/null +++ b/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/ByteGroup.java @@ -0,0 +1,26 @@ +package org.jeecg.modules.qywx.message.utils; + +import java.util.ArrayList; + +class ByteGroup { + ArrayList<Byte> byteContainer = new ArrayList<Byte>(); + + 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/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/PKCS7Encoder.java b/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/PKCS7Encoder.java new file mode 100644 index 0000000..2bda687 --- /dev/null +++ b/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/PKCS7Encoder.java @@ -0,0 +1,67 @@ +/** + * 瀵逛紒涓氬井淇″彂閫佺粰浼佷笟鍚庡彴鐨勬秷鎭姞瑙e瘑绀轰緥浠g爜. + * + * @copyright Copyright (c) 1998-2014 Tencent Inc. + */ + +// ------------------------------------------------------------------------ + +package org.jeecg.modules.qywx.message.utils; + +import java.nio.charset.Charset; +import java.util.Arrays; + +/** + * 鎻愪緵鍩轰簬PKCS7绠楁硶鐨勫姞瑙e瘑鎺ュ彛. + */ +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); + } + + /** + * 鍒犻櫎瑙e瘑鍚庢槑鏂囩殑琛ヤ綅瀛楃 + * + * @param decrypted 瑙e瘑鍚庣殑鏄庢枃 + * @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/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/SHA1.java b/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/SHA1.java new file mode 100644 index 0000000..30f04f2 --- /dev/null +++ b/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/SHA1.java @@ -0,0 +1,61 @@ +/** + * 瀵逛紒涓氬井淇″彂閫佺粰浼佷笟鍚庡彴鐨勬秷鎭姞瑙e瘑绀轰緥浠g爜. + * + * @copyright Copyright (c) 1998-2014 Tencent Inc. + */ + +// ------------------------------------------------------------------------ + +package org.jeecg.modules.qywx.message.utils; + +import java.security.MessageDigest; +import java.util.Arrays; + +/** + * SHA1 class + * + * 璁$畻娑堟伅绛惧悕鎺ュ彛. + */ +class SHA1 { + + /** + * 鐢⊿HA1绠楁硶鐢熸垚瀹夊叏绛惧悕 + * @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/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/Sample.java b/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/Sample.java new file mode 100644 index 0000000..41b870a --- /dev/null +++ b/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/Sample.java @@ -0,0 +1,136 @@ +package org.jeecg.modules.qywx.message.utils; + +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; + +public class Sample { + + public static void main(String[] args) throws Exception { + String sToken = "QDG6eK"; + String sCorpID = "wx5823bf96d3bd56c7"; + String sEncodingAESKey = "jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C"; + + WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(sToken, sEncodingAESKey, sCorpID); + /* + ------------浣跨敤绀轰緥涓�锛氶獙璇佸洖璋僓RL--------------- + *浼佷笟寮�鍚洖璋冩ā寮忔椂锛屼紒涓氬井淇′細鍚戦獙璇乽rl鍙戦�佷竴涓猤et璇锋眰 + 鍋囪鐐瑰嚮楠岃瘉鏃讹紝浼佷笟鏀跺埌绫讳技璇锋眰锛� + * GET /cgi-bin/wxpush?msg_signature=5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3×tamp=1409659589&nonce=263014780&echostr=P9nAzCzyDtyTWESHep1vC5X9xho%2FqYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp%2B4RPcs8TgAE7OaBO%2BFZXvnaqQ%3D%3D + * HTTP/1.1 Host: qy.weixin.qq.com + + 鎺ユ敹鍒拌璇锋眰鏃讹紝浼佷笟搴� 1.瑙f瀽鍑篏et璇锋眰鐨勫弬鏁帮紝鍖呮嫭娑堟伅浣撶鍚�(msg_signature)锛屾椂闂存埑(timestamp)锛岄殢鏈烘暟瀛椾覆(nonce)浠ュ強浼佷笟寰俊鎺ㄩ�佽繃鏉ョ殑闅忔満鍔犲瘑瀛楃涓�(echostr), + 杩欎竴姝ユ敞鎰忎綔URL瑙g爜銆� + 2.楠岃瘉娑堟伅浣撶鍚嶇殑姝g‘鎬� + 3. 瑙e瘑鍑篹chostr鍘熸枃锛屽皢鍘熸枃褰撲綔Get璇锋眰鐨剅esponse锛岃繑鍥炵粰浼佷笟寰俊 + 绗�2锛�3姝ュ彲浠ョ敤浼佷笟寰俊鎻愪緵鐨勫簱鍑芥暟VerifyURL鏉ュ疄鐜般�� + + */ + // 瑙f瀽鍑簎rl涓婄殑鍙傛暟鍊煎涓嬶細 + // String sVerifyMsgSig = HttpUtils.ParseUrl("msg_signature"); + String sVerifyMsgSig = "5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3"; + // String sVerifyTimeStamp = HttpUtils.ParseUrl("timestamp"); + String sVerifyTimeStamp = "1409659589"; + // String sVerifyNonce = HttpUtils.ParseUrl("nonce"); + String sVerifyNonce = "263014780"; + // String sVerifyEchoStr = HttpUtils.ParseUrl("echostr"); + String sVerifyEchoStr = "P9nAzCzyDtyTWESHep1vC5X9xho/qYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp+4RPcs8TgAE7OaBO+FZXvnaqQ=="; + String sEchoStr; //闇�瑕佽繑鍥炵殑鏄庢枃 + try { + sEchoStr = wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp, + sVerifyNonce, sVerifyEchoStr); + System.out.println("verifyurl echostr: " + sEchoStr); + // 楠岃瘉URL鎴愬姛锛屽皢sEchoStr杩斿洖 + // HttpUtils.SetResponse(sEchoStr); + } catch (Exception e) { + //楠岃瘉URL澶辫触锛岄敊璇師鍥犺鏌ョ湅寮傚父 + e.printStackTrace(); + } + + /* + ------------浣跨敤绀轰緥浜岋細瀵圭敤鎴峰洖澶嶇殑娑堟伅瑙e瘑--------------- + 鐢ㄦ埛鍥炲娑堟伅鎴栬�呯偣鍑讳簨浠跺搷搴旀椂锛屼紒涓氫細鏀跺埌鍥炶皟娑堟伅锛屾娑堟伅鏄粡杩囦紒涓氬井淇″姞瀵嗕箣鍚庣殑瀵嗘枃浠ost褰㈠紡鍙戦�佺粰浼佷笟锛屽瘑鏂囨牸寮忚鍙傝�冨畼鏂规枃妗� + 鍋囪浼佷笟鏀跺埌浼佷笟寰俊鐨勫洖璋冩秷鎭涓嬶細 + POST /cgi-bin/wxpush? msg_signature=477715d11cdb4164915debcba66cb864d751f3e6×tamp=1409659813&nonce=1372623149 HTTP/1.1 + Host: qy.weixin.qq.com + Content-Length: 613 + <xml> <ToUserName><![CDATA[wx5823bf96d3bd56c7]]></ToUserName><Encrypt><![CDATA[RypEvHKD8QQKFhvQ6QleEB4J58tiPdvo+rtK1I9qca6aM/wvqnLSV5zEPeusUiX5L5X/0lWfrf0QADHHhGd3QczcdCUpj911L3vg3W/sYYvuJTs3TUUkSUXxaccAS0qhxchrRYt66wiSpGLYL42aM6A8dTT+6k4aSknmPj48kzJs8qLjvd4Xgpue06DOdnLxAUHzM6+kDZ+HMZfJYuR+LtwGc2hgf5gsijff0ekUNXZiqATP7PF5mZxZ3Izoun1s4zG4LUMnvw2r+KqCKIw+3IQH03v+BCA9nMELNqbSf6tiWSrXJB3LAVGUcallcrw8V2t9EL4EhzJWrQUax5wLVMNS0+rUPA3k22Ncx4XXZS9o0MBH27Bo6BpNelZpS+/uh9KsNlY6bHCmJU9p8g7m3fVKn28H3KDYA5Pl/T8Z1ptDAVe0lXdQ2YoyyH2uyPIGHBZZIs2pDBS8R07+qN+E7Q==]]></Encrypt> + <AgentID><![CDATA[218]]></AgentID> + </xml> + + 浼佷笟鏀跺埌post璇锋眰涔嬪悗搴旇 1.瑙f瀽鍑簎rl涓婄殑鍙傛暟锛屽寘鎷秷鎭綋绛惧悕(msg_signature)锛屾椂闂存埑(timestamp)浠ュ強闅忔満鏁板瓧涓�(nonce) + 2.楠岃瘉娑堟伅浣撶鍚嶇殑姝g‘鎬с�� + 3.灏唒ost璇锋眰鐨勬暟鎹繘琛寈ml瑙f瀽锛屽苟灏�<Encrypt>鏍囩鐨勫唴瀹硅繘琛岃В瀵嗭紝瑙e瘑鍑烘潵鐨勬槑鏂囧嵆鏄敤鎴峰洖澶嶆秷鎭殑鏄庢枃锛屾槑鏂囨牸寮忚鍙傝�冨畼鏂规枃妗� + 绗�2锛�3姝ュ彲浠ョ敤浼佷笟寰俊鎻愪緵鐨勫簱鍑芥暟DecryptMsg鏉ュ疄鐜般�� + */ + // String sReqMsgSig = HttpUtils.ParseUrl("msg_signature"); + String sReqMsgSig = "477715d11cdb4164915debcba66cb864d751f3e6"; + // String sReqTimeStamp = HttpUtils.ParseUrl("timestamp"); + String sReqTimeStamp = "1409659813"; + // String sReqNonce = HttpUtils.ParseUrl("nonce"); + String sReqNonce = "1372623149"; + // post璇锋眰鐨勫瘑鏂囨暟鎹� + // sReqData = HttpUtils.PostData(); + String sReqData = "<xml><ToUserName><![CDATA[wx5823bf96d3bd56c7]]></ToUserName><Encrypt><![CDATA[RypEvHKD8QQKFhvQ6QleEB4J58tiPdvo+rtK1I9qca6aM/wvqnLSV5zEPeusUiX5L5X/0lWfrf0QADHHhGd3QczcdCUpj911L3vg3W/sYYvuJTs3TUUkSUXxaccAS0qhxchrRYt66wiSpGLYL42aM6A8dTT+6k4aSknmPj48kzJs8qLjvd4Xgpue06DOdnLxAUHzM6+kDZ+HMZfJYuR+LtwGc2hgf5gsijff0ekUNXZiqATP7PF5mZxZ3Izoun1s4zG4LUMnvw2r+KqCKIw+3IQH03v+BCA9nMELNqbSf6tiWSrXJB3LAVGUcallcrw8V2t9EL4EhzJWrQUax5wLVMNS0+rUPA3k22Ncx4XXZS9o0MBH27Bo6BpNelZpS+/uh9KsNlY6bHCmJU9p8g7m3fVKn28H3KDYA5Pl/T8Z1ptDAVe0lXdQ2YoyyH2uyPIGHBZZIs2pDBS8R07+qN+E7Q==]]></Encrypt><AgentID><![CDATA[218]]></AgentID></xml>"; + + try { + String sMsg = wxcpt.DecryptMsg(sReqMsgSig, sReqTimeStamp, sReqNonce, sReqData); + System.out.println("after decrypt msg: " + sMsg); + // TODO: 瑙f瀽鍑烘槑鏂噚ml鏍囩鐨勫唴瀹硅繘琛屽鐞� + // For example: + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + StringReader sr = new StringReader(sMsg); + InputSource is = new InputSource(sr); + Document document = db.parse(is); + + Element root = document.getDocumentElement(); + NodeList nodelist1 = root.getElementsByTagName("Content"); + String Content = nodelist1.item(0).getTextContent(); + System.out.println("Content锛�" + Content); + + } catch (Exception e) { + // TODO + // 瑙e瘑澶辫触锛屽け璐ュ師鍥犺鏌ョ湅寮傚父 + e.printStackTrace(); + } + + /* + ------------浣跨敤绀轰緥涓夛細浼佷笟鍥炲鐢ㄦ埛娑堟伅鐨勫姞瀵�--------------- + 浼佷笟琚姩鍥炲鐢ㄦ埛鐨勬秷鎭篃闇�瑕佽繘琛屽姞瀵嗭紝骞朵笖鎷兼帴鎴愬瘑鏂囨牸寮忕殑xml涓层�� + 鍋囪浼佷笟闇�瑕佸洖澶嶇敤鎴风殑鏄庢枃濡備笅锛� + <xml> + <ToUserName><![CDATA[mycreate]]></ToUserName> + <FromUserName><![CDATA[wx5823bf96d3bd56c7]]></FromUserName> + <CreateTime>1348831860</CreateTime> + <MsgType><![CDATA[text]]></MsgType> + <Content><![CDATA[this is a test]]></Content> + <MsgId>1234567890123456</MsgId> + <AgentID>128</AgentID> + </xml> + + 涓轰簡灏嗘娈垫槑鏂囧洖澶嶇粰鐢ㄦ埛锛屼紒涓氬簲锛� 1.鑷繁鐢熸垚鏃堕棿鏃堕棿鎴�(timestamp),闅忔満鏁板瓧涓�(nonce)浠ヤ究鐢熸垚娑堟伅浣撶鍚嶏紝涔熷彲浠ョ洿鎺ョ敤浠庝紒涓氬井淇$殑post url涓婅В鏋愬嚭鐨勫搴斿�笺�� + 2.灏嗘槑鏂囧姞瀵嗗緱鍒板瘑鏂囥�� 3.鐢ㄥ瘑鏂囷紝姝ラ1鐢熸垚鐨則imestamp,nonce鍜屼紒涓氬湪浼佷笟寰俊璁惧畾鐨則oken鐢熸垚娑堟伅浣撶鍚嶃�� 4.灏嗗瘑鏂囷紝娑堟伅浣撶鍚嶏紝鏃堕棿鎴筹紝闅忔満鏁板瓧涓叉嫾鎺ユ垚xml鏍煎紡鐨勫瓧绗︿覆锛屽彂閫佺粰浼佷笟銆� + 浠ヤ笂2锛�3锛�4姝ュ彲浠ョ敤浼佷笟寰俊鎻愪緵鐨勫簱鍑芥暟EncryptMsg鏉ュ疄鐜般�� + */ + String sRespData = "<xml><ToUserName><![CDATA[mycreate]]></ToUserName><FromUserName><![CDATA[wx5823bf96d3bd56c7]]></FromUserName><CreateTime>1348831860</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[this is a test]]></Content><MsgId>1234567890123456</MsgId><AgentID>128</AgentID></xml>"; + try{ + String sEncryptMsg = wxcpt.EncryptMsg(sRespData, sReqTimeStamp, sReqNonce); + System.out.println("after encrypt sEncrytMsg: " + sEncryptMsg); + // 鍔犲瘑鎴愬姛 + // TODO: + // HttpUtils.SetResponse(sEncryptMsg); + } + catch(Exception e) + { + e.printStackTrace(); + // 鍔犲瘑澶辫触 + } + + } +} diff --git a/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/WXBizMsgCrypt.java b/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/WXBizMsgCrypt.java new file mode 100644 index 0000000..cded1d1 --- /dev/null +++ b/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/WXBizMsgCrypt.java @@ -0,0 +1,289 @@ +/** + * 瀵逛紒涓氬井淇″彂閫佺粰浼佷笟鍚庡彴鐨勬秷鎭姞瑙e瘑绀轰緥浠g爜. + * + * @copyright Copyright (c) 1998-2014 Tencent Inc. + */ + +// ------------------------------------------------------------------------ + +/** + * 閽堝org.apache.commons.codec.binary.Base64锛� + * 闇�瑕佸鍏ユ灦鍖卌ommons-codec-1.9锛堟垨commons-codec-1.8绛夊叾浠栫増鏈級 + * 瀹樻柟涓嬭浇鍦板潃锛歨ttp://commons.apache.org/proper/commons-codec/download_codec.cgi + */ +package org.jeecg.modules.qywx.message.utils; + +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; + +/** + * 鎻愪緵鎺ユ敹鍜屾帹閫佺粰浼佷笟寰俊娑堟伅鐨勫姞瑙e瘑鎺ュ彛(UTF8缂栫爜鐨勫瓧绗︿覆). + * <ol> + * <li>绗笁鏂瑰洖澶嶅姞瀵嗘秷鎭粰浼佷笟寰俊</li> + * <li>绗笁鏂规敹鍒颁紒涓氬井淇″彂閫佺殑娑堟伅锛岄獙璇佹秷鎭殑瀹夊叏鎬э紝骞跺娑堟伅杩涜瑙e瘑銆�</li> + * </ol> + * 璇存槑锛氬紓甯竕ava.security.InvalidKeyException:illegal Key Size鐨勮В鍐虫柟妗� + * <ol> + * <li>鍦ㄥ畼鏂圭綉绔欎笅杞絁CE鏃犻檺鍒舵潈闄愮瓥鐣ユ枃浠讹紙JDK7鐨勪笅杞藉湴鍧�锛� + * http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html</li> + * <li>涓嬭浇鍚庤В鍘嬶紝鍙互鐪嬪埌local_policy.jar鍜孶S_export_policy.jar浠ュ強readme.txt</li> + * <li>濡傛灉瀹夎浜咼RE锛屽皢涓や釜jar鏂囦欢鏀惧埌%JRE_HOME%\lib\security鐩綍涓嬭鐩栧師鏉ョ殑鏂囦欢</li> + * <li>濡傛灉瀹夎浜咼DK锛屽皢涓や釜jar鏂囦欢鏀惧埌%JDK_HOME%\jre\lib\security鐩綍涓嬭鐩栧師鏉ユ枃浠�</li> + * </ol> + */ +public class WXBizMsgCrypt { + static Charset CHARSET = Charset.forName("utf-8"); + Base64 base64 = new Base64(); + byte[] aesKey; + String token; + String receiveid; + + /** + * 鏋勯�犲嚱鏁� + * @param token 浼佷笟寰俊鍚庡彴锛屽紑鍙戣�呰缃殑token + * @param encodingAesKey 浼佷笟寰俊鍚庡彴锛屽紑鍙戣�呰缃殑EncodingAESKey + * @param receiveid, 涓嶅悓鍦烘櫙鍚箟涓嶅悓锛岃瑙佹枃妗� + * + * @throws AesException 鎵ц澶辫触锛岃鏌ョ湅璇ュ紓甯哥殑閿欒鐮佸拰鍏蜂綋鐨勯敊璇俊鎭� + */ + public WXBizMsgCrypt(String token, String encodingAesKey, String receiveid) throws AesException { + if (encodingAesKey.length() != 43) { + throw new AesException(AesException.IllegalAesKey); + } + + this.token = token; + this.receiveid = receiveid; + 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 鍔犲瘑鍚巄ase64缂栫爜鐨勫瓧绗︿覆 + * @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[] receiveidBytes = receiveid.getBytes(CHARSET); + + // randomStr + networkBytesOrder + text + receiveid + byteCollector.addBytes(randomStrBytes); + byteCollector.addBytes(networkBytesOrder); + byteCollector.addBytes(textBytes); + byteCollector.addBytes(receiveidBytes); + + // ... + pad: 浣跨敤鑷畾涔夌殑濉厖鏂瑰紡瀵规槑鏂囪繘琛岃ˉ浣嶅~鍏� + byte[] padBytes = PKCS7Encoder.encode(byteCollector.size()); + byteCollector.addBytes(padBytes); + + // 鑾峰緱鏈�缁堢殑瀛楄妭娴�, 鏈姞瀵� + byte[] unencrypted = byteCollector.toBytes(); + + try { + // 璁剧疆鍔犲瘑妯″紡涓篈ES鐨凜BC妯″紡 + 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 瑙e瘑寰楀埌鐨勬槑鏂� + * @throws AesException aes瑙e瘑澶辫触 + */ + String decrypt(String text) throws AesException { + byte[] original; + try { + // 璁剧疆瑙e瘑妯″紡涓篈ES鐨凜BC妯″紡 + Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); + SecretKeySpec key_spec = new SecretKeySpec(aesKey, "AES"); + IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16)); + //IvParameterSpec iv = new IvParameterSpec(new byte[16]); // 鍥哄畾 IV + cipher.init(Cipher.DECRYPT_MODE, key_spec, iv); + + // 浣跨敤BASE64瀵瑰瘑鏂囪繘琛岃В鐮� + byte[] encrypted = Base64.decodeBase64(text); + + // 瑙e瘑 + original = cipher.doFinal(encrypted); + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.DecryptAESError); + } + + String xmlContent, from_receiveid; + try { + // 鍘婚櫎琛ヤ綅瀛楃 + byte[] bytes = PKCS7Encoder.decode(original); + + // 鍒嗙16浣嶉殢鏈哄瓧绗︿覆,缃戠粶瀛楄妭搴忓拰receiveid + byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20); + + int xmlLength = recoverNetworkBytesOrder(networkOrder); + + 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涓嶇浉鍚岀殑鎯呭喌 + if (!from_receiveid.equals(receiveid)) { + throw new AesException(AesException.ValidateCorpidError); + } + return xmlContent; + + } + + /** + * 灏嗕紒涓氬井淇″洖澶嶇敤鎴风殑娑堟伅鍔犲瘑鎵撳寘. + * <ol> + * <li>瀵硅鍙戦�佺殑娑堟伅杩涜AES-CBC鍔犲瘑</li> + * <li>鐢熸垚瀹夊叏绛惧悕</li> + * <li>灏嗘秷鎭瘑鏂囧拰瀹夊叏绛惧悕鎵撳寘鎴恱ml鏍煎紡</li> + * </ol> + * + * @param replyMsg 浼佷笟寰俊寰呭洖澶嶇敤鎴风殑娑堟伅锛寈ml鏍煎紡鐨勫瓧绗︿覆 + * @param timeStamp 鏃堕棿鎴筹紝鍙互鑷繁鐢熸垚锛屼篃鍙互鐢║RL鍙傛暟鐨則imestamp + * @param nonce 闅忔満涓诧紝鍙互鑷繁鐢熸垚锛屼篃鍙互鐢║RL鍙傛暟鐨刵once + * + * @return 鍔犲瘑鍚庣殑鍙互鐩存帴鍥炲鐢ㄦ埛鐨勫瘑鏂囷紝鍖呮嫭msg_signature, timestamp, nonce, encrypt鐨剎ml鏍煎紡鐨勫瓧绗︿覆 + * @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; + } + + /** + * 妫�楠屾秷鎭殑鐪熷疄鎬э紝骞朵笖鑾峰彇瑙e瘑鍚庣殑鏄庢枃. + * <ol> + * <li>鍒╃敤鏀跺埌鐨勫瘑鏂囩敓鎴愬畨鍏ㄧ鍚嶏紝杩涜绛惧悕楠岃瘉</li> + * <li>鑻ラ獙璇侀�氳繃锛屽垯鎻愬彇xml涓殑鍔犲瘑娑堟伅</li> + * <li>瀵规秷鎭繘琛岃В瀵�</li> + * </ol> + * + * @param msgSignature 绛惧悕涓诧紝瀵瑰簲URL鍙傛暟鐨刴sg_signature + * @param timeStamp 鏃堕棿鎴筹紝瀵瑰簲URL鍙傛暟鐨則imestamp + * @param nonce 闅忔満涓诧紝瀵瑰簲URL鍙傛暟鐨刵once + * @param postData 瀵嗘枃锛屽搴擯OST璇锋眰鐨勬暟鎹� + * + * @return 瑙e瘑鍚庣殑鍘熸枃 + * @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()); + + // 鍜孶RL涓殑绛惧悕姣旇緝鏄惁鐩哥瓑 + // System.out.println("绗笁鏂规敹鍒癠RL涓殑绛惧悕锛�" + msg_sign); + // System.out.println("绗笁鏂规牎楠岀鍚嶏細" + signature); + if (!signature.equals(msgSignature)) { + throw new AesException(AesException.ValidateSignatureError); + } + + // 瑙e瘑 + String result = decrypt(encrypt[1].toString()); + return result; + } + + /** + * 楠岃瘉URL + * @param msgSignature 绛惧悕涓诧紝瀵瑰簲URL鍙傛暟鐨刴sg_signature + * @param timeStamp 鏃堕棿鎴筹紝瀵瑰簲URL鍙傛暟鐨則imestamp + * @param nonce 闅忔満涓诧紝瀵瑰簲URL鍙傛暟鐨刵once + * @param echoStr 闅忔満涓诧紝瀵瑰簲URL鍙傛暟鐨別chostr + * + * @return 瑙e瘑涔嬪悗鐨別chostr + * @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/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/XMLParse.java b/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/XMLParse.java new file mode 100644 index 0000000..89ed6a4 --- /dev/null +++ b/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/utils/XMLParse.java @@ -0,0 +1,104 @@ +/** + * 瀵逛紒涓氬井淇″彂閫佺粰浼佷笟鍚庡彴鐨勬秷鎭姞瑙e瘑绀轰緥浠g爜. + * + * @copyright Copyright (c) 1998-2014 Tencent Inc. + */ + +// ------------------------------------------------------------------------ + +package org.jeecg.modules.qywx.message.utils; + +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 { + + /** + * 鎻愬彇鍑簒ml鏁版嵁鍖呬腑鐨勫姞瀵嗘秷鎭� + * @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 鐢熸垚鐨剎ml瀛楃涓� + */ + 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); + + } +} diff --git a/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/vo/TemplateCard.java b/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/vo/TemplateCard.java new file mode 100644 index 0000000..cf7772c --- /dev/null +++ b/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/vo/TemplateCard.java @@ -0,0 +1,20 @@ +package org.jeecg.modules.qywx.message.vo; + +import lombok.Data; + +/** + * 浼佷笟寰俊妯℃澘鍗$墖娑堟伅锛堟枃鏈�氱煡鍨嬶級 + */ +@Data +public class TemplateCard { + private String touser;//鎴愬憳ID鍒楄〃锛堟秷鎭帴鏀惰�咃紝澶氫釜鎺ユ敹鑰呯敤鈥榺鈥欏垎闅旓紝鏈�澶氭敮鎸�1000涓級銆傜壒娈婃儏鍐碉細鎸囧畾涓篅all锛屽垯鍚戝叧娉ㄨ浼佷笟搴旂敤鐨勫叏閮ㄦ垚鍛樺彂閫� + private String toparty;//閮ㄩ棬ID鍒楄〃锛屽涓帴鏀惰�呯敤鈥榺鈥欏垎闅旓紝鏈�澶氭敮鎸�100涓�傚綋touser涓篅all鏃跺拷鐣ユ湰鍙� + private String totag;//鏍囩ID鍒楄〃锛屽涓帴鏀惰�呯敤鈥榺鈥欏垎闅旓紝鏈�澶氭敮鎸�100涓�傚綋touser涓篅all鏃跺拷鐣ユ湰鍙傛暟 + private int agentid;//浼佷笟搴旂敤鐨刬d锛屾暣鍨嬨�傚彲鍦ㄥ簲鐢ㄧ殑璁剧疆椤甸潰鏌ョ湅 + private TemplateCardEntity template_card;//娑堟伅瀹炰綋 + private String enable_id_trans;// 鍚� 琛ㄧず鏄惁寮�鍚痠d杞瘧锛�0琛ㄧず鍚︼紝1琛ㄧず鏄紝榛樿0 + private String enable_duplicate_check;// 鍚� 琛ㄧず鏄惁寮�鍚噸澶嶆秷鎭鏌ワ紝0琛ㄧず鍚︼紝1琛ㄧず鏄紝榛樿0 + private String duplicate_check_interval;// 鍚� 琛ㄧず鏄惁閲嶅娑堟伅妫�鏌ョ殑鏃堕棿闂撮殧锛岄粯璁�1800s锛屾渶澶т笉瓒呰繃4灏忔椂 + private String msgtype = "template_card";//娑堟伅绫诲瀷锛屾鏃跺浐瀹氫负锛歵emplate_card + +} diff --git a/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/vo/TemplateCardEntity.java b/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/vo/TemplateCardEntity.java new file mode 100644 index 0000000..8fdf7b5 --- /dev/null +++ b/lxzn-module-system/lxzn-system-biz/src/main/java/org/jeecg/modules/qywx/message/vo/TemplateCardEntity.java @@ -0,0 +1,94 @@ +package org.jeecg.modules.qywx.message.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.List; + +/** + * 妯℃澘鍗$墖娑堟伅瀹炰綋 + */ +@Data +public class TemplateCardEntity { + private Source source;//鍚� 鍗$墖鏉ユ簮鏍峰紡淇℃伅锛屼笉闇�瑕佹潵婧愭牱寮忓彲涓嶅~鍐� + private ActionMenu action_menu;//鍚� 鍗$墖鍙充笂瑙掓洿澶氭搷浣滄寜閽� + private String task_id;//鍚� 浠诲姟id锛屽悓涓�涓簲鐢ㄤ换鍔d涓嶈兘閲嶅锛屽彧鑳界敱鏁板瓧銆佸瓧姣嶅拰鈥淿-@鈥濈粍鎴愶紝鏈�闀�128瀛楄妭锛屽~浜哸ction_menu瀛楁鐨勮瘽鏈瓧娈靛繀濉� + private MainTitle main_title; + private QuoteArea quote_area;//鍚� 寮曠敤鏂囩尞鏍峰紡 + private EmphasisContent emphasis_content; + private String sub_title_text;//浜岀骇鏅�氭枃鏈紝寤鸿涓嶈秴杩�160涓瓧锛岋紙鏀寔id杞瘧锛� + private List<HorizontalContent> horizontal_content_list; + private List<Jump> jump_list;//鍚� 璺宠浆鎸囧紩鏍峰紡鐨勫垪琛紝璇ュ瓧娈靛彲涓虹┖鏁扮粍锛屼絾鏈夋暟鎹殑璇濋渶纭瀵瑰簲瀛楁鏄惁蹇呭~锛屽垪琛ㄩ暱搴︿笉瓒呰繃3 + private CardAction card_action;//鏄� 鏁翠綋鍗$墖鐨勭偣鍑昏烦杞簨浠讹紝text_notice蹇呭~鏈瓧娈� + //鍥炬枃灞曠ず鍨嬪崱绁ㄧ嫭鏈夌殑鍙傛暟 + private CardImage card_image; + private String card_type = "text_notice";// 妯℃澘鍗$墖绫诲瀷锛屾枃鏈�氱煡鍨嬪崱鐗囧~鍐� "text_notice" + + @Data + public static class Source { + private String icon_url;//鍚� 鏉ユ簮鍥剧墖鐨剈rl锛屾潵婧愬浘鐗囩殑灏哄寤鸿涓�72*72 + private String desc;//鍚� 鏉ユ簮鍥剧墖鐨勬弿杩帮紝寤鸿涓嶈秴杩�20涓瓧锛岋紙鏀寔id杞瘧锛� + private String desc_color;//鍚� 鏉ユ簮鏂囧瓧鐨勯鑹诧紝鐩墠鏀寔锛�0(榛樿) 鐏拌壊锛�1 榛戣壊锛�2 绾㈣壊锛�3 缁胯壊 + } + + @Data + public static class ActionMenu { + private String desc;//鍚� 鏇村鎿嶄綔鐣岄潰鐨勬弿杩� + private List<ActionList> action_list;//鏄� 鎿嶄綔鍒楄〃锛屽垪琛ㄩ暱搴﹀彇鍊艰寖鍥翠负 [1, 3] + + @Data + @AllArgsConstructor + public static class ActionList { + private String key;//鏄� 鎿嶄綔key鍊硷紝鐢ㄦ埛鐐瑰嚮鍚庯紝浼氫骇鐢熷洖璋冧簨浠跺皢鏈弬鏁颁綔涓篍ventKey杩斿洖锛屽洖璋冧簨浠朵細甯︿笂璇ey鍊硷紝鏈�闀挎敮鎸�1024瀛楄妭锛屼笉鍙噸澶� + private String text;//鏄� 鎿嶄綔鐨勬弿杩版枃妗� + } + } + + @Data + public static class MainTitle { + private String title;//鍚� 涓�绾ф爣棰橈紝寤鸿涓嶈秴杩�36涓瓧锛屾枃鏈�氱煡鍨嬪崱鐗囨湰瀛楁闈炲繀濉紝浣嗕笉鍙湰瀛楁鍜宻ub_title_text閮戒笉濉紝锛堟敮鎸乮d杞瘧锛� + private String desc;//鍚� 鏍囬杈呭姪淇℃伅锛屽缓璁笉瓒呰繃44涓瓧锛岋紙鏀寔id杞瘧锛� + } + + @Data + private class QuoteArea { + private int type;//鍚� 寮曠敤鏂囩尞鏍峰紡鍖哄煙鐐瑰嚮浜嬩欢锛�0鎴栦笉濉唬琛ㄦ病鏈夌偣鍑讳簨浠讹紝1 浠h〃璺宠浆url锛�2 浠h〃璺宠浆灏忕▼搴� + private String url;//鍚� 鐐瑰嚮璺宠浆鐨剈rl锛宷uote_area.type鏄�1鏃跺繀濉� + private String title;//鍚� 寮曠敤鏂囩尞鏍峰紡鐨勬爣棰� + private String quote_text;//鍚� 寮曠敤鏂囩尞鏍峰紡鐨勫紩鐢ㄦ枃妗� + } + + @Data + private class EmphasisContent { + private String title;//鍚� 鍏抽敭鏁版嵁鏍峰紡鐨勬暟鎹唴瀹癸紝寤鸿涓嶈秴杩�14涓瓧 + private String desc;//鍚� 鍏抽敭鏁版嵁鏍峰紡鐨勬暟鎹弿杩板唴瀹癸紝寤鸿涓嶈秴杩�22涓瓧 + } + + @Data + public static class HorizontalContent { + private String keyname;//鏄� 浜岀骇鏍囬锛屽缓璁笉瓒呰繃5涓瓧 + private String value;//鍚� 浜岀骇鏂囨湰锛屽鏋渉orizontal_content_list.type鏄�2锛岃瀛楁浠h〃鏂囦欢鍚嶇О锛堣鍖呭惈鏂囦欢绫诲瀷锛夛紝寤鸿涓嶈秴杩�30涓瓧锛岋紙鏀寔id杞瘧锛� + } + + @Data + @AllArgsConstructor + public static class Jump { + private int type;//鍚� 璺宠浆閾炬帴绫诲瀷锛�0鎴栦笉濉唬琛ㄤ笉鏄摼鎺ワ紝1 浠h〃璺宠浆url锛�2 浠h〃璺宠浆灏忕▼搴� + private String title;//鏄� 璺宠浆閾炬帴鏍峰紡鐨勬枃妗堝唴瀹癸紝寤鸿涓嶈秴杩�18涓瓧 + private String url;//鍚� 璺宠浆閾炬帴鐨剈rl锛宩ump_list.type鏄�1鏃跺繀濉� + } + + @Data + public static class CardAction { + private int type;//鏄� 璺宠浆浜嬩欢绫诲瀷锛�1 浠h〃璺宠浆url锛�2 浠h〃鎵撳紑灏忕▼搴忋�倀ext_notice鍗$墖妯$増涓瀛楁鍙栧�艰寖鍥翠负[1,2] + private String url;//鍚� 璺宠浆浜嬩欢鐨剈rl锛宑ard_action.type鏄�1鏃跺繀濉� + private String appid;//鍚� 璺宠浆浜嬩欢鐨勫皬绋嬪簭鐨刟ppid锛屽繀椤绘槸涓庡綋鍓嶅簲鐢ㄥ叧鑱旂殑灏忕▼搴忥紝card_action.type鏄�2鏃跺繀濉� + private String pagepath;//鍚� 璺宠浆浜嬩欢鐨勫皬绋嬪簭鐨刾agepath锛宑ard_action.type鏄�2鏃堕�夊~ + } + + @Data + public static class CardImage { + private String url;//鏄� 鍥剧墖鐨剈rl + private String aspect_ratio;//鍚� 鍥剧墖鐨勫楂樻瘮锛屽楂樻瘮瑕佸皬浜�2.25锛屽ぇ浜�1.3锛屼笉濉鍙傛暟榛樿1.3 + } +} -- Gitblit v1.9.3