java对接微信公众号

java对接微信公众号

前言

微信官方提供了非常完善的接入文档,如果想了解文档的具体内容,可直接浏览微信开发文档。但是为了方便开发,一般不会直接去根据微信开发文档进行开发,github上有许多开源项目对微信开发文档进行了封装,可自行下载。

一、填写服务器配置

java对接微信公众号

参数解析

  1. URL:是开发者用来接收微信消息和事件推送的接口URL(POST),另外,同一个路径下的GET请求为token验证接口。
  2. Token:可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)
  3. EncodingAESKey:由开发者手动填写或随机生成,将用作消息体加解密密钥,推荐使用兼容模式

二、开发验证服务器有效性接口

GET请求进行验证token有效性

    @ApiOperation("微信配置推送地址验证token")
    @RequestMapping(value = "/wx", method = RequestMethod.GET)
    public String checkSignature(@RequestParam(name = "signature", required = false) String signature,
                                 @RequestParam(name = "nonce", required = false) String nonce,
                                 @RequestParam(name = "timestamp", required = false) String timestamp,
                                 @RequestParam(name = "echostr", required = false) String echostr) {
        System.out.println("get有消息来了!");

        // 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败
        log.info("接收参数:" + "===========signature:" + signature + "===========timestamp:" + timestamp + "===========nonce:" + nonce);
        Boolean flag = false;
        try {
            flag = SignUtil.checkSignature(signature, timestamp, nonce);
        } catch (Exception e) {
            flag = false;
        }
        if (flag) {
            log.info("接入成功");
            return echostr;
        }
        log.error("接入失败");
        return "";
    }

Utils

package com.riskeys.sd.custom.utils;

import java.security.*;
import java.util.Arrays;
import java.util.Map;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * 提供签名服务。 目前提供的签名服务包括:SHA-1,SHA256,SHA256withRSA.
 */
@Slf4j
@Component
public class SignUtil {

    private static String CHARSET_UTF8 = "UTF-8";
    private static String SHA256withRSA = "SHA256withRSA";

    private static String checkToken;

    @Value("${demo.weChat.checkToken}")
    private void setCheckToken(String token) {
        checkToken = token;
    }

    public SignUtil() {
    }


    /**
     * SHA256withRSA算法签名
     *
     * @param text          需要签名的字符串
     * @param privateKeyStr 私钥
     * @return
     */
    public static String makeSignSHA256withRSA(String text, String privateKeyStr) {
        if (text == null || privateKeyStr == null) {
            return "";
        }
        try {
            // 获取私钥
            PrivateKey privateKey = OcpRSAUtil.getPrivateKey(privateKeyStr);
            // 进行签名服务
            Signature signature = Signature.getInstance(SHA256withRSA);
            signature.initSign(privateKey);
            signature.update(text.getBytes(CHARSET_UTF8));
            byte[] signedData = signature.sign();
            return Base64.encodeBase64String(signedData);
        } catch (Exception e) {
            log.error("签名失败", e);
        }
        return "";
    }

    /**
     * SHA256withRSA算法验签
     * @param text         待验签的字符串
     * @param signedData   待验签的签名
     * @param publicKeyStr 公钥
     * @return
     */
    public static boolean verifySHA256withRSA(String text, String signedData, String publicKeyStr) {
        if (text == null || signedData == null || publicKeyStr == null) {
            return false;
        }
        try {
            PublicKey publicKey = OcpRSAUtil.getPublicKey(publicKeyStr);
            Signature signature = Signature.getInstance(SHA256withRSA);
            signature.initVerify(publicKey);
            signature.update(text.getBytes(CHARSET_UTF8));
            return signature.verify(Base64.decodeBase64(signedData));
        } catch (Exception e) {
            log.error("验签失败", e);
        }
        return false;
    }

    /**
     * 验证签名
     *
     * @param signature
     * @param timestamp
     * @param nonce
     * @return
     */
    public static boolean checkSignature(String signature, String timestamp, String nonce) {
        String[] arr = new String[]{checkToken, timestamp, nonce};
        // 将token、timestamp、nonce三个参数进行字典序排序
        Arrays.sort(arr);
        StringBuilder content = new StringBuilder();
        for (int i = 0; i < arr.length; i++) {
            content.append(arr[i]);
        }
        MessageDigest md = null;
        String tmpStr = null;

        try {
            md = MessageDigest.getInstance("SHA-1");
            // 将三个参数字符串拼接成一个字符串进行sha1加密
            byte[] digest = md.digest(content.toString().getBytes());
            tmpStr = byteToStr(digest);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        content = null;
        // 将sha1加密后的字符串可与signature对比,标识该请求来源于微信
        return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
    }

    /**
     * 将字节数组转换为十六进制字符串
     *
     * @param byteArray
     * @return
     */
    private static String byteToStr(byte[] byteArray) {
        String strDigest = "";
        for (int i = 0; i < byteArray.length; i++) {
            strDigest += byteToHexStr(byteArray[i]);
        }
        return strDigest;
    }

    /**
     * 将字节转换为十六进制字符串
     *
     * @param mByte
     * @return
     */
    private static String byteToHexStr(byte mByte) {
        char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        char[] tempArr = new char[2];
        tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
        tempArr[1] = Digit[mByte & 0X0F];

        String s = new String(tempArr);
        return s;
    }

}

三、开发处理微信事件推送接口

POST请求进行事件处理(业务处理)

    @ApiOperation("接收微信推送通知(扫码关注,取消关注,扫码已关注,普通互动消息)")
    @PostMapping(value = "/wx", produces = "application/xml")
    public String pushEventsPost(HttpServletRequest request) throws Exception {
        String method = request.getMethod();
        if ("POST".equals(method)) {
            weChatEventsService.pushEvents(request);
        }
        return "success";
    }

总结

  1. 在公众号开发平台配置url、token、等信息时,需要 启用配置。
  2. 需区分GET和POST请求各自的作用
  3. 测试环境进行调试的时候,http请求验证通过,但是在消息推送时有可能转成https请求,需要进行相应处理,才能正确接收事件推送。
上一篇:某音web端的_signature参数破解


下一篇:批量爬取酷狗音乐js逆向解析思路