Merge branch 'master' of https://git.iwulin.tech/quyixiao/eb-service-api
t push
This commit is contained in:
commit
bb6924347f
@ -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 {
|
||||
// 将 token、timestamp、nonce、echostr 按字典序排序
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
|
||||
@ -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×tamp=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();
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user