微信公众号接口配置信息需要我们后台提供接口校验sign并返回对应信息才能配置成功,官网样例是php那么java如何实现呢。结合网络一些解决办法,这边做个总结。
编写Handler接收微信请求并返回echostr
用SPRING MVC 编写一个请求处理方法。注意mapping 要跟配置的url 匹配上。例如配置如下:
那么我们需要编写一个 /weixin/verityToken的处理方法如下:
@RequestMapping(value = "/verityToken")
public void verityToken(HttpServletRequest request, HttpServletResponse response) throws Exception {
log.info("验证TOKEN,请求进来了...");
// 微信加密签名
String signature = request.getParameter("signature");
// 时间戳
String timestamp = request.getParameter("timestamp");
// 随机数
String nonce = request.getParameter("nonce");
// 随机字符串
String echostr = request.getParameter("echostr");
//******************校验token**********************//
PrintWriter out = response.getWriter();
List<String> paramList = new ArrayList<>();
paramList.add(nonce);
paramList.add(timestamp);
paramList.add(WECHAT_TOKEN);
//按字节排序
Collections.sort(paramList);
//按顺序拼接字符串
StringBuilder stringBuilder = new StringBuilder();
paramList.forEach(stringBuilder::append);
String signMe = ShaUtil.getSHA1_Oth(stringBuilder.toString());
log.info("本系统签名为:{}",signMe);
if(signMe.equals(signature)) {
out.print(echostr);
}else {
out.print("");
}
out.close();
}
这里主要做三件事,第一获取微信入参,第二校验签名,第三返回echostr。如果你觉得麻烦,其实只要把微信的入参echostr直接返回给他就可以验证通过了,但是不太安全。
接下来看看如何校验签名
注意点 需要校验签名以及签名方法
这里他是把 nonce timestamp 和token的值排序后拼接在一起通过 sha1 签名
但是需要注意的是他直接将值排序并签名而非组装拼接为key=value&key2=value。
假如:
nonce=51469832×tamp=1652693511&token=cfq
那么最终签名字符串是
165269351151469832chenfuqiang
为了方便查看我加个分隔符
1652693511[timestamp值] 51469832 [nonce值] cfq[token值]
以下附带Sha签名工具
package ecan.netapp.util;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* @author cfq
* 2022/5/16 17:06
*/
public class ShaUtil {
private static final String SHA_1 = "SHA-1";
private static final String SHA_224 = "SHA-224";
private static final String SHA_256 = "SHA-256";
private static final String SHA_384 = "SHA-384";
private static final String SHA_512 = "SHA-512";
private static final char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
public static String getSHA1(String painText, boolean uppercase) {
return getSha(painText, SHA_1, uppercase);
}
/**
* Takes the raw bytes from the digest and formats them correct.
*
* @param bytes the raw bytes from the digest.
* @return the formatted bytes.
*/
private static String getFormattedText(byte[] bytes) {
int len = bytes.length;
StringBuilder buf = new StringBuilder(len * 2);
// 把密文转换成十六进制的字符串形式
for (int j = 0; j < len; j++) {
buf.append(hexDigits[(bytes[j] >> 4) & 0x0f]);
buf.append(hexDigits[bytes[j] & 0x0f]);
}
return buf.toString();
}
public static String getSHA1_Oth(String str) {
if (str == null) {
return null;
}
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
messageDigest.update(str.getBytes());
return getFormattedText(messageDigest.digest());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String getSHA224(String painText, boolean uppercase) {
return getSha(painText, SHA_224, uppercase);
}
public static String getSHA256(String painText, boolean uppercase) {
return getSha(painText, SHA_256, uppercase);
}
public static String getSHA384(String painText, boolean uppercase) {
return getSha(painText, SHA_384, uppercase);
}
public static String getSHA512(String painText, boolean uppercase) {
return getSha(painText, SHA_512, uppercase);
}
/**
* 利用Java原生摘要实现SHA加密(支持大小写,默认小写)
*
* @param plainText 要加密的数据
* @param algorithm 要使用的算法(SHA-1,SHA-2224,SHA-256,SHA-384,SHA-512)
* @param uppercase 是否转为大写
* @return
*/
private static String getSha(String plainText, String algorithm, boolean uppercase) {
//输入的字符串转换成字节数组
byte[] bytes = plainText.getBytes(StandardCharsets.UTF_8);
MessageDigest messageDigest;
try {
//获得SHA转换器
messageDigest = MessageDigest.getInstance(algorithm);
//bytes是输入字符串转换得到的字节数组
messageDigest.update(bytes);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("SHA签名过程中出现错误,算法异常");
}
//转换并返回结果,也是字节数组,包含16个元素
byte[] digest = messageDigest.digest();
//字符数组转换成字符串返回
String result = byteArrayToHexString(digest);
//转换大写
return uppercase ? result.toUpperCase() : result;
}
/**
* 将字节数组转为16进制字符串
*
* @param bytes 要转换的字节数组
* @return
*/
private static String byteArrayToHexString(byte[] bytes) {
StringBuilder builder = new StringBuilder();
for (byte b : bytes) {
//java.lang.Integer.toHexString() 方法的参数是int(32位)类型,
//如果输入一个byte(8位)类型的数字,这个方法会把这个数字的高24为也看作有效位,就会出现错误
//如果使用& 0XFF操作,可以把高24位置0以避免这样错误
String temp = Integer.toHexString(b & 0xFF);
if (temp.length() == 1) {
//1得到一位的进行补0操作
builder.append("0");
}
builder.append(temp);
}
return builder.toString();
}
}