企业微信推送suite_ticket对接

企业微信推送suite_ticket对接企业微信推送suite_ticket对接

大家好,欢迎来到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

(0)

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信