- 支持解析Apple签名JWT并提取交易信息 - 新增processRenewNotification处理续订通知 - 添加测试用JWT生成、解析及发送重试记录示例 - 移除废弃ApplePayUtil,统一走新验证逻辑
89 lines
8.6 KiB
Java
89 lines
8.6 KiB
Java
package com.yolo.keyborad;
|
||
|
||
import io.jsonwebtoken.Claims;
|
||
import io.jsonwebtoken.Jws;
|
||
import io.jsonwebtoken.Jwts;
|
||
import org.json.JSONArray;
|
||
import org.json.JSONObject;
|
||
|
||
import java.io.ByteArrayInputStream;
|
||
import java.security.PublicKey;
|
||
import java.security.cert.CertificateFactory;
|
||
import java.security.cert.X509Certificate;
|
||
import java.util.Base64;
|
||
|
||
import static org.bouncycastle.asn1.x509.ObjectDigestInfo.publicKey;
|
||
|
||
public class JwtParser {
|
||
|
||
public static void main(String[] args) throws Exception {
|
||
String signedPayload = "eyJhbGciOiJFUzI1NiIsIng1YyI6WyJNSUlFTVRDQ0E3YWdBd0lCQWdJUVI4S0h6ZG41NTRaL1VvcmFkTng5dHpBS0JnZ3Foa2pPUFFRREF6QjFNVVF3UWdZRFZRUURERHRCY0hCc1pTQlhiM0pzWkhkcFpHVWdSR1YyWld4dmNHVnlJRkpsYkdGMGFXOXVjeUJEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURUxNQWtHQTFVRUN3d0NSell4RXpBUkJnTlZCQW9NQ2tGd2NHeGxJRWx1WXk0eEN6QUpCZ05WQkFZVEFsVlRNQjRYRFRJMU1Ea3hPVEU1TkRRMU1Wb1hEVEkzTVRBeE16RTNORGN5TTFvd2daSXhRREErQmdOVkJBTU1OMUJ5YjJRZ1JVTkRJRTFoWXlCQmNIQWdVM1J2Y21VZ1lXNWtJR2xVZFc1bGN5QlRkRzl5WlNCU1pXTmxhWEIwSUZOcFoyNXBibWN4TERBcUJnTlZCQXNNSTBGd2NHeGxJRmR2Y214a2QybGtaU0JFWlhabGJHOXdaWElnVW1Wc1lYUnBiMjV6TVJNd0VRWURWUVFLREFwQmNIQnNaU0JKYm1NdU1Rc3dDUVlEVlFRR0V3SlZVekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCTm5WdmhjdjdpVCs3RXg1dEJNQmdyUXNwSHpJc1hSaTBZeGZlazdsdjh3RW1qL2JIaVd0TndKcWMyQm9IenNRaUVqUDdLRklJS2c0WTh5MC9ueW51QW1qZ2dJSU1JSUNCREFNQmdOVkhSTUJBZjhFQWpBQU1COEdBMVVkSXdRWU1CYUFGRDh2bENOUjAxREptaWc5N2JCODVjK2xrR0taTUhBR0NDc0dBUVVGQndFQkJHUXdZakF0QmdnckJnRUZCUWN3QW9ZaGFIUjBjRG92TDJObGNuUnpMbUZ3Y0d4bExtTnZiUzkzZDJSeVp6WXVaR1Z5TURFR0NDc0dBUVVGQnpBQmhpVm9kSFJ3T2k4dmIyTnpjQzVoY0hCc1pTNWpiMjB2YjJOemNEQXpMWGQzWkhKbk5qQXlNSUlCSGdZRFZSMGdCSUlCRlRDQ0FSRXdnZ0VOQmdvcWhraUc5Mk5rQlFZQk1JSCtNSUhEQmdnckJnRUZCUWNDQWpDQnRneUJzMUpsYkdsaGJtTmxJRzl1SUhSb2FYTWdZMlZ5ZEdsbWFXTmhkR1VnWW5rZ1lXNTVJSEJoY25SNUlHRnpjM1Z0WlhNZ1lXTmpaWEIwWVc1alpTQnZaaUIwYUdVZ2RHaGxiaUJoY0hCc2FXTmhZbXhsSUhOMFlXNWtZWEprSUhSbGNtMXpJR0Z1WkNCamIyNWthWFJwYjI1eklHOW1JSFZ6WlN3Z1kyVnlkR2xtYVdOaGRHVWdjRzlzYVdONUlHRnVaQ0JqWlhKMGFXWnBZMkYwYVc5dUlIQnlZV04wYVdObElITjBZWFJsYldWdWRITXVNRFlHQ0NzR0FRVUZCd0lCRmlwb2RIUndPaTh2ZDNkM0xtRndjR3hsTG1OdmJTOWpaWEowYVdacFkyRjBaV0YxZEdodmNtbDBlUzh3SFFZRFZSME9CQllFRklGaW9HNHdNTVZBMWt1OXpKbUdOUEFWbjNlcU1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBUUJnb3Foa2lHOTJOa0Jnc0JCQUlGQURBS0JnZ3Foa2pPUFFRREF3TnBBREJtQWpFQStxWG5SRUM3aFhJV1ZMc0x4em5qUnBJelBmN1ZIejlWL0NUbTgrTEpsclFlcG5tY1B2R0xOY1g2WFBubGNnTEFBakVBNUlqTlpLZ2c1cFE3OWtuRjRJYlRYZEt2OHZ1dElETVhEbWpQVlQzZEd2RnRzR1J3WE95d1Iya1pDZFNyZmVvdCIsIk1JSURGakNDQXB5Z0F3SUJBZ0lVSXNHaFJ3cDBjMm52VTRZU3ljYWZQVGp6Yk5jd0NnWUlLb1pJemowRUF3TXdaekViTUJrR0ExVUVBd3dTUVhCd2JHVWdVbTl2ZENCRFFTQXRJRWN6TVNZd0pBWURWUVFMREIxQmNIQnNaU0JEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURVRNQkVHQTFVRUNnd0tRWEJ3YkdVZ1NXNWpMakVMTUFrR0ExVUVCaE1DVlZNd0hoY05NakV3TXpFM01qQXpOekV3V2hjTk16WXdNekU1TURBd01EQXdXakIxTVVRd1FnWURWUVFERER0QmNIQnNaU0JYYjNKc1pIZHBaR1VnUkdWMlpXeHZjR1Z5SUZKbGJHRjBhVzl1Y3lCRFpYSjBhV1pwWTJGMGFXOXVJRUYxZEdodmNtbDBlVEVMTUFrR0ExVUVDd3dDUnpZeEV6QVJCZ05WQkFvTUNrRndjR3hsSUVsdVl5NHhDekFKQmdOVkJBWVRBbFZUTUhZd0VBWUhLb1pJemowQ0FRWUZLNEVFQUNJRFlnQUVic1FLQzk0UHJsV21aWG5YZ3R4emRWSkw4VDBTR1luZ0RSR3BuZ24zTjZQVDhKTUViN0ZEaTRiQm1QaENuWjMvc3E2UEYvY0djS1hXc0w1dk90ZVJoeUo0NXgzQVNQN2NPQithYW85MGZjcHhTdi9FWkZibmlBYk5nWkdoSWhwSW80SDZNSUgzTUJJR0ExVWRFd0VCL3dRSU1BWUJBZjhDQVFBd0h3WURWUjBqQkJnd0ZvQVV1N0Rlb1ZnemlKcWtpcG5ldnIzcnI5ckxKS3N3UmdZSUt3WUJCUVVIQVFFRU9qQTRNRFlHQ0NzR0FRVUZCekFCaGlwb2RIUndPaTh2YjJOemNDNWhjSEJzWlM1amIyMHZiMk56Y0RBekxXRndjR3hsY205dmRHTmhaek13TndZRFZSMGZCREF3TGpBc29DcWdLSVltYUhSMGNEb3ZMMk55YkM1aGNIQnNaUzVqYjIwdllYQndiR1Z5YjI5MFkyRm5NeTVqY213d0hRWURWUjBPQkJZRUZEOHZsQ05SMDFESm1pZzk3YkI4NWMrbGtHS1pNQTRHQTFVZER3RUIvd1FFQXdJQkJqQVFCZ29xaGtpRzkyTmtCZ0lCQkFJRkFEQUtCZ2dxaGtqT1BRUURBd05vQURCbEFqQkFYaFNxNUl5S29nTUNQdHc0OTBCYUI2NzdDYUVHSlh1ZlFCL0VxWkdkNkNTamlDdE9udU1UYlhWWG14eGN4ZmtDTVFEVFNQeGFyWlh2TnJreFUzVGtVTUkzM3l6dkZWVlJUNHd4V0pDOTk0T3NkY1o0K1JHTnNZRHlSNWdtZHIwbkRHZz0iLCJNSUlDUXpDQ0FjbWdBd0lCQWdJSUxjWDhpTkxGUzVVd0NnWUlLb1pJemowRUF3TXdaekViTUJrR0ExVUVBd3dTUVhCd2JHVWdVbTl2ZENCRFFTQXRJRWN6TVNZd0pBWURWUVFMREIxQmNIQnNaU0JEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURVRNQkVHQTFVRUNnd0tRWEJ3YkdVZ1NXNWpMakVMTUFrR0ExVUVCaE1DVlZNd0hoY05NVFF3TkRNd01UZ3hPVEEyV2hjTk16a3dORE13TVRneE9UQTJXakJuTVJzd0dRWURWUVFEREJKQmNIQnNaU0JTYjI5MElFTkJJQzBnUnpNeEpqQWtCZ05WQkFzTUhVRndjR3hsSUVObGNuUnBabWxqWVhScGIyNGdRWFYwYUc5eWFYUjVNUk13RVFZRFZRUUtEQXBCY0hCc1pTQkpibU11TVFzd0NRWURWUVFHRXdKVlV6QjJNQkFHQnlxR1NNNDlBZ0VHQlN1QkJBQWlBMklBQkpqcEx6MUFjcVR0a3lKeWdSTWMzUkNWOGNXalRuSGNGQmJaRHVXbUJTcDNaSHRmVGpqVHV4eEV0WC8xSDdZeVlsM0o2WVJiVHpCUEVWb0EvVmhZREtYMUR5eE5CMGNUZGRxWGw1ZHZNVnp0SzUxN0lEdll1VlRaWHBta09sRUtNYU5DTUVBd0hRWURWUjBPQkJZRUZMdXczcUZZTTRpYXBJcVozcjY5NjYvYXl5U3JNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01Bb0dDQ3FHU000OUJBTURBMmdBTUdVQ01RQ0Q2Y0hFRmw0YVhUUVkyZTN2OUd3T0FFWkx1Tit5UmhIRkQvM21lb3locG12T3dnUFVuUFdUeG5TNGF0K3FJeFVDTUcxbWloREsxQTNVVDgyTlF6NjBpbU9sTTI3amJkb1h0MlFmeUZNbStZaGlkRGtMRjF2TFVhZ002QmdENTZLeUtBPT0iXX0.eyJ0cmFuc2FjdGlvbklkIjoiMjAwMDAwMTA4MDM4MDQ2NSIsIm9yaWdpbmFsVHJhbnNhY3Rpb25JZCI6IjIwMDAwMDEwODAzODA0NjUiLCJ3ZWJPcmRlckxpbmVJdGVtSWQiOiIyMDAwMDAwMTIxNzI0MTU4IiwiYnVuZGxlSWQiOiJjb20ubG92ZUtleS5ueXgiLCJwcm9kdWN0SWQiOiJjb20ubG92ZUtleS5ueXguMm1vbnRoIiwic3Vic2NyaXB0aW9uR3JvdXBJZGVudGlmaWVyIjoiMjE4MzA1OTYiLCJwdXJjaGFzZURhdGUiOjE3NjU1MjgwNDkwMDAsIm9yaWdpbmFsUHVyY2hhc2VEYXRlIjoxNzY1NTI4MDUwMDAwLCJleHBpcmVzRGF0ZSI6MTc2NTUyODY0OTAwMCwicXVhbnRpdHkiOjEsInR5cGUiOiJBdXRvLVJlbmV3YWJsZSBTdWJzY3JpcHRpb24iLCJpbkFwcE93bmVyc2hpcFR5cGUiOiJQVVJDSEFTRUQiLCJzaWduZWREYXRlIjoxNzY1NTQ1Njg4ODk3LCJlbnZpcm9ubWVudCI6IlNhbmRib3giLCJ0cmFuc2FjdGlvblJlYXNvbiI6IlBVUkNIQVNFIiwic3RvcmVmcm9udCI6IlVTQSIsInN0b3JlZnJvbnRJZCI6IjE0MzQ0MSIsInByaWNlIjoyOTkwLCJjdXJyZW5jeSI6IlVTRCIsImFwcFRyYW5zYWN0aW9uSWQiOiI3MDUxMDE2OTUxMDg3NDA0MjcifQ.ANGQBobIroeZfZnBPiOSDJonND8-7PnPRbW29G1Nhfj_-BxLMvRu-Qu6SzmVDEJ9NrE-2EPH1R1tk-yV-TuPbg";
|
||
// 从 JWT header 中提取公钥
|
||
PublicKey publicKey = extractPublicKeyFromJWT(signedPayload);
|
||
// 解码 JWT(使用公钥验证)
|
||
Jws<io.jsonwebtoken.Claims> claimsJws = Jwts.parserBuilder()
|
||
.setSigningKey(publicKey)
|
||
.build()
|
||
.parseClaimsJws(signedPayload);
|
||
Claims claims = claimsJws.getBody();
|
||
claims.forEach((key, value) -> System.out.println(key + ": " + value));
|
||
String notificationType = claims.get("notificationType", String.class);
|
||
String notificationUuid = claims.get("notificationUUID", String.class);
|
||
|
||
System.out.println("Notification Type: " + notificationType);
|
||
System.out.println("Notification UUID: " + notificationUuid);
|
||
|
||
|
||
|
||
|
||
// 解码 JWT(使用公钥验证)
|
||
// 注意,Jwts.parserBuilder() 代码没有包含,实际解码时应该使用你原来的方法
|
||
// System.out.println("Extracted Public Key: " + publicKey);
|
||
}
|
||
|
||
/**
|
||
* 从 JWT 的 x5c header 中提取公钥
|
||
*/
|
||
private static PublicKey extractPublicKeyFromJWT(String jwt) throws Exception {
|
||
// 解析 JWT header(不验证签名)
|
||
String[] parts = jwt.split("\\.");
|
||
if (parts.length < 2) {
|
||
throw new IllegalArgumentException("Invalid JWT format");
|
||
}
|
||
|
||
// 解码 header
|
||
String headerJson = new String(Base64.getUrlDecoder().decode(parts[0])); // 使用 URL 安全的 Base64 解码
|
||
JSONObject header = new JSONObject(headerJson);
|
||
|
||
// 获取 x5c 证书链(第一个证书包含公钥)
|
||
JSONArray x5cArray = header.getJSONArray("x5c");
|
||
if (x5cArray.length() == 0) {
|
||
throw new IllegalArgumentException("No x5c certificates found in JWT header");
|
||
}
|
||
|
||
// 获取第一个证书(Base64 编码,标准格式非URL安全格式)
|
||
String certBase64 = x5cArray.getString(0);
|
||
|
||
// 调试信息
|
||
System.out.println("Cert Base64 length: " + certBase64.length());
|
||
System.out.println("First 50 chars: " + certBase64.substring(0, Math.min(50, certBase64.length())));
|
||
|
||
// x5c 中的证书使用标准 Base64 编码(非 URL 安全编码)
|
||
byte[] certBytes;
|
||
try {
|
||
certBytes = Base64.getDecoder().decode(certBase64); // 使用标准 Base64 解码
|
||
System.out.println("Decoded cert bytes length: " + certBytes.length);
|
||
} catch (IllegalArgumentException e) {
|
||
System.err.println("Base64 decode error: " + e.getMessage());
|
||
throw new Exception("Failed to decode certificate from x5c", e);
|
||
}
|
||
|
||
// 生成 X509 证书并提取公钥
|
||
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
|
||
X509Certificate cert = (X509Certificate) certFactory.generateCertificate(
|
||
new ByteArrayInputStream(certBytes)
|
||
);
|
||
return cert.getPublicKey();
|
||
}
|
||
}
|