大家好,欢迎来到IT知识分享网。
企业微信推送suite_ticket对接,由于微信文档不详细,很多地方还有错误,所以对接的时候很是痛苦。通过查阅各种文档,加上整合demo才最终对接成功,拿到了suite_ticket。
推送suite_ticket的文档说是一个POST接口,其实还有一个验证的GET接口,而且需要URL一致的。比如我的接口都是“/suite/receive”。
下面接对接通过代码展示,首先是对接参数:
private final String sToken = "Token";
private final String sCorpID = "CorpID";
private final String suiteID = "SuiteID";
private final String sEncodingAESKey = "EncodingAESKey";
这些参数都是可以通过企业微信管理后台拿到的,比如
然后是企业微信参数解析类
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Base64;
/** * 描述:微信参数解密工具 * * @author 罗锅 * @date 2021/9/2 11:44 */
public class WXBizMsgCrypt {
byte[] aesKey;
String token;
String receiveId;
/** * 构造函数 * * @param token 企业微信后台,开发者设置的token * @param encodingAesKey 企业微信后台,开发者设置的EncodingAESKey * @param receiveId, 不同场景含义不同,详见文档 */
public WXBizMsgCrypt(String token, String encodingAesKey, String receiveId) {
this.token = token;
this.receiveId = receiveId;
aesKey = Base64.getDecoder().decode(encodingAesKey + "=");
}
/** * 验证并获取解密后数据 * * @param msgSignature 签名串,对应URL参数的msg_signature * @param timeStamp 时间戳,对应URL参数的timestamp * @param nonce 随机串,对应URL参数的nonce * @param echoStr 随机串,对应URL参数的echostr * @return 解密之后的echoString * @throws Exception 执行失败,请查看该异常的错误码和具体的错误信息 */
public String verifyAndGetData(String msgSignature, String timeStamp, String nonce, String echoStr)
throws Exception {
String signature = getSignature(token, timeStamp, nonce, echoStr);
if (!signature.equals(msgSignature)) {
throw new Exception("参数验签不通过");
}
return decrypt(echoStr);
}
/** * 获取参数签名 * * @param token * @param timestamp * @param nonce * @param encrypt * @return * @throws Exception */
private String getSignature(String token, String timestamp, String nonce, String encrypt) throws Exception {
String[] array = new String[]{
token, timestamp, nonce, encrypt};
StringBuilder sb = new StringBuilder();
// 字符串排序
Arrays.sort(array);
for (String s : array) {
sb.append(s);
}
String str = sb.toString();
// SHA1签名生成
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(str.getBytes());
byte[] digest = md.digest();
StringBuilder hexStringBuilder = new StringBuilder();
String shaHex;
for (byte b : digest) {
shaHex = Integer.toHexString(b & 0xFF);
if (shaHex.length() < 2) {
hexStringBuilder.append(0);
}
hexStringBuilder.append(shaHex);
}
return hexStringBuilder.toString();
}
/** * 对密文进行解密. * * @param text 需要解密的密文 * @return 解密得到的明文 * @throws Exception aes解密失败 */
private String decrypt(String text) throws Exception {
// 设置解密模式为AES的CBC模式
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec secretKeySpec = new SecretKeySpec(aesKey, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
// 使用BASE64对密文进行解码
byte[] encrypted = Base64.getDecoder().decode(text);
// 解密
byte[] original = cipher.doFinal(encrypted);
// 去除补位字符
byte[] bytes = decode(original);
// 分离16位随机字符串,网络字节序和receiveId
byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);
int xmlLength = recoverNetworkBytesOrder(networkOrder);
String xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), StandardCharsets.UTF_8);
String fromReceiveId = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length),
StandardCharsets.UTF_8);
// receiveId不相同的情况
if (!fromReceiveId.equals(receiveId)) {
throw new Exception("receiveId不相同");
}
return xmlContent;
}
/** * 还原4个字节的网络字节序 * * @param orderBytes * @return */
int recoverNetworkBytesOrder(byte[] orderBytes) {
int sourceNumber = 0;
int length = 4;
for (int i = 0; i < length; i++) {
sourceNumber <<= 8;
sourceNumber |= orderBytes[i] & 0xff;
}
return sourceNumber;
}
/** * 删除解密后明文的补位字符 * * @param decrypted 解密后的明文 * @return 删除补位字符后的明文 */
private byte[] decode(byte[] decrypted) {
int pad = decrypted[decrypted.length - 1];
int min = 1;
int max = 32;
if (pad < min || pad > max) {
pad = 0;
}
return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
}
}
然后是验证接口,推送suite_ticket是这样的,要先用GET请求验证接口,验证通过了以后才可以保存并推送suite_ticket。
@GetMapping("/suite/receive")
public String suiteReceive(@RequestParam(value = "msg_signature") String msgSignature,
@RequestParam(value = "timestamp") String timestamp, @RequestParam(value = "nonce") String nonce,
@RequestParam(value = "echostr") String data) {
System.out.println("GET################################");
System.out.println("msgSignature:" + msgSignature);
System.out.println("timestamp:" + timestamp);
System.out.println("nonce:" + nonce);
System.out.println("data:" + data);
try {
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(sToken, sEncodingAESKey, sCorpID);
String sEchoStr = wxcpt.verifyAndGetData(msgSignature, timestamp, nonce, data);
System.out.println("aesDecode:" + sEchoStr);
// 将解密后获取的参数直接返回就行
return sEchoStr;
} catch (Exception e) {
e.printStackTrace();
}
}
验证通过后,就可以刷新Ticket了。
@PostMapping("/suite/receive")
public String suiteReceivePost(HttpServletRequest request,
@RequestParam(value = "msg_signature") String msgSignature,
@RequestParam(value = "timestamp") String timestamp, @RequestParam(value = "nonce") String nonce) {
System.out.println("POST################################");
System.out.println("msgSignature:" + msgSignature);
System.out.println("timestamp:" + timestamp);
System.out.println("nonce:" + nonce);
String result = null;
try {
String xmlString = getXMLString(request);
System.out.println("data:" + xmlString);
String encryptData = XML.toJSONObject(xmlString).getJSONObject("xml").getStr("Encrypt");
System.out.println("encryptData:" + encryptData);
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(sToken, sEncodingAESKey, suiteID);
String sEchoStr = wxcpt.verifyAndGetData(msgSignature, timestamp, nonce, encryptData);
;
System.out.println("sEchoStr:" + sEchoStr);
JSONObject jsonObject = XML.toJSONObject(sEchoStr).getJSONObject("xml");
System.out.println("jsonObject:" + jsonObject.toString());
String infoType = jsonObject.getStr("InfoType");
System.out.println("infoType:" + infoType);
// 获取到的suiteTicket
String suiteTicket = jsonObject.getStr("SuiteTicket");
System.out.println("suiteTicket:" + suiteTicket);
} catch (Exception e) {
e.printStackTrace();
}
// 该接口返回success
return "success";
}
可以看到,GET和POST接口的URI是一致的,不同的是参数和返回内容。验证接口是GET请求,加密内容是URL里获取的,接口直接返回解密内容就行。推送suite_ticket接口是POST请求,加密内容是在请求体里的,接口返回“success”表示成功。
推送suite_ticket接口回调成功以后,每半小时推送一次,注意保存suite_ticket后续接口调用。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/10626.html