t push
This commit is contained in:
quyixiao 2026-05-06 13:54:15 +08:00
commit bb6924347f
3 changed files with 232 additions and 1 deletions

View File

@ -0,0 +1,116 @@
package com.heyu.api.data.utils;
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;
/**
* 微信回调验证和加解密工具类
*/
public class WeChatCallbackUtil {
/**
* 验证回调 URL
*
* @param token Token
* @param timestamp 时间戳
* @param nonce 随机字符串
* @param msgSignature 消息签名
* @param echostr 加密的随机字符串
* @param encodingAESKey EncodingAESKey
* @return 解密后的 echostr
* @throws Exception 验证失败或解密失败
*/
public static String verifyURL(String token, String timestamp,
String nonce, String msgSignature,
String echostr, String encodingAESKey) throws Exception {
// 1. 验证签名
String signature = generateSignature(token, timestamp, nonce, echostr);
if (!signature.equals(msgSignature)) {
throw new Exception("签名验证失败");
}
// 2. 解密 echostr
String result = decrypt(echostr, encodingAESKey);
return result;
}
/**
* 生成签名
*/
public static String generateSignature(String token, String timestamp,
String nonce, String echostr) throws Exception {
// tokentimestampnonceechostr 按字典序排序
String[] arr = {token, timestamp, nonce, echostr};
Arrays.sort(arr);
// 拼接字符串
StringBuilder sb = new StringBuilder();
for (String s : arr) {
sb.append(s);
}
// SHA1 加密
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] digest = md.digest(sb.toString().getBytes(StandardCharsets.UTF_8));
// 转换为十六进制字符串
StringBuilder hexString = new StringBuilder();
for (byte b : digest) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
/**
* AES 解密
*/
public static String decrypt(String encryptedData, String encodingAESKey) throws Exception {
// Base64 解码
byte[] keyBytes = Base64.getDecoder().decode(encodingAESKey);
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);
// 提取前 16 字节作为 IV
byte[] iv = new byte[16];
System.arraycopy(encryptedBytes, 0, iv, 0, 16);
// 提取加密内容
byte[] ciphertext = new byte[encryptedBytes.length - 16];
System.arraycopy(encryptedBytes, 16, ciphertext, 0, ciphertext.length);
// AES-256-CBC 解密
SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
byte[] decrypted = cipher.doFinal(ciphertext);
// 去除填充并提取消息内容
// 4 字节是消息长度最后是随机字符串
int contentLength = bytesToInt(decrypted, 0);
byte[] content = new byte[contentLength];
System.arraycopy(decrypted, 4, content, 0, contentLength);
return new String(content, StandardCharsets.UTF_8);
}
/**
* 字节数组转整数大端序
*/
private static int bytesToInt(byte[] bytes, int offset) {
return ((bytes[offset] & 0xFF) << 24) |
((bytes[offset + 1] & 0xFF) << 16) |
((bytes[offset + 2] & 0xFF) << 8) |
(bytes[offset + 3] & 0xFF);
}
}

View File

@ -61,7 +61,7 @@ public class ZhenZhenLogAop {
public final static List<String> not_login_urls = Arrays.asList(user_login_url, anonymous_login_url,
"/app/weixin/payNotify","/app/weixin/refundNotify");
"/app/weixin/payNotify","/app/weixin/refundNotify","/app/weixin/customer/verify","/app/weixin/customer/receiveMessage");
public Map<String,String> classHasAnnotation = new HashMap();

View File

@ -0,0 +1,115 @@
package com.heyu.api.controller.vv;
import com.heyu.api.data.utils.WeChatCallbackUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@Slf4j
@RestController
@RequestMapping("/app/weixin/customer")
public class AppWeiXinCustomerNotifyController {
public static String token = "9Iosbni8qAU8Kcmy8tgRLw";
public static String encodingAESKey = "miBhTSRiwCgn9aYoa8d1TDu62GYFV2aE03vXl2Rrgv6";
/**
* 验证回调 URL (GET 请求)
*/
// https://api.1024api.com/api-interface/app/weixin/customer/verify?msg_signature=5392430904602161909&timestamp=1737681600&nonce=1234567890&echostr=1234567890
// https://api.1024api.com/api-interface/app/weixin/customer/verify
@GetMapping("/verify")
public String verify(@RequestParam("msg_signature") String msgSignature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
@RequestParam("echostr") String echostr) {
try {
// 验证并解密
String result = WeChatCallbackUtil.verifyURL(
token, timestamp, nonce, msgSignature, echostr, encodingAESKey
);
return result;
} catch (Exception e) {
e.printStackTrace();
return "验证失败";
}
}
/**
* 接收回调消息 (POST 请求)
*/
// /app/weixin/customer/receiveMessage
@PostMapping("/receiveMessage")
public String receiveMessage(@RequestParam("msg_signature") String msgSignature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
HttpServletRequest request) {
try {
// 读取请求体
String encryptedMessage = getRequestBody(request);
// 验证签名
if (!verifySignature(msgSignature, timestamp, nonce, encryptedMessage)) {
return "签名验证失败";
}
// 解密消息
String decryptedMessage = WeChatCallbackUtil.decrypt(
encryptedMessage, encodingAESKey
);
// 解析 XML 消息
// TODO: 解析消息内容并处理
// 返回成功响应
return "success";
} catch (Exception e) {
e.printStackTrace();
return "处理失败";
}
}
/**
* 验证签名
*/
private boolean verifySignature(String msgSignature, String timestamp,
String nonce, String encryptedMessage) {
try {
String signature = WeChatCallbackUtil.generateSignature(
token, timestamp, nonce, encryptedMessage
);
return signature.equals(msgSignature);
} catch (Exception e) {
return false;
}
}
/**
* 获取请求体内容
*/
private String getRequestBody(HttpServletRequest request) throws IOException {
StringBuilder sb = new StringBuilder();
try (java.io.BufferedReader reader = request.getReader()) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
}
return sb.toString();
}
}