java对接微信公众号
前言
微信官方提供了非常完善的接入文档,如果想了解文档的具体内容,可直接浏览微信开发文档。但是为了方便开发,一般不会直接去根据微信开发文档进行开发,github上有许多开源项目对微信开发文档进行了封装,可自行下载。
一、填写服务器配置
参数解析
- URL:是开发者用来接收微信消息和事件推送的接口URL(POST),另外,同一个路径下的GET请求为token验证接口。
- Token:可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)
- 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";
}
总结
- 在公众号开发平台配置url、token、等信息时,需要 启用配置。
- 需区分GET和POST请求各自的作用
- 测试环境进行调试的时候,http请求验证通过,但是在消息推送时有可能转成https请求,需要进行相应处理,才能正确接收事件推送。