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 = "eyJhbGciOiJFUzI1NiIsIng1YyI6WyJNSUlFTVRDQ0E3YWdBd0lCQWdJUVI4S0h6ZG41NTRaL1VvcmFkTng5dHpBS0JnZ3Foa2pPUFFRREF6QjFNVVF3UWdZRFZRUURERHRCY0hCc1pTQlhiM0pzWkhkcFpHVWdSR1YyWld4dmNHVnlJRkpsYkdGMGFXOXVjeUJEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURUxNQWtHQTFVRUN3d0NSell4RXpBUkJnTlZCQW9NQ2tGd2NHeGxJRWx1WXk0eEN6QUpCZ05WQkFZVEFsVlRNQjRYRFRJMU1Ea3hPVEU1TkRRMU1Wb1hEVEkzTVRBeE16RTNORGN5TTFvd2daSXhRREErQmdOVkJBTU1OMUJ5YjJRZ1JVTkRJRTFoWXlCQmNIQWdVM1J2Y21VZ1lXNWtJR2xVZFc1bGN5QlRkRzl5WlNCU1pXTmxhWEIwSUZOcFoyNXBibWN4TERBcUJnTlZCQXNNSTBGd2NHeGxJRmR2Y214a2QybGtaU0JFWlhabGJHOXdaWElnVW1Wc1lYUnBiMjV6TVJNd0VRWURWUVFLREFwQmNIQnNaU0JKYm1NdU1Rc3dDUVlEVlFRR0V3SlZVekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCTm5WdmhjdjdpVCs3RXg1dEJNQmdyUXNwSHpJc1hSaTBZeGZlazdsdjh3RW1qL2JIaVd0TndKcWMyQm9IenNRaUVqUDdLRklJS2c0WTh5MC9ueW51QW1qZ2dJSU1JSUNCREFNQmdOVkhSTUJBZjhFQWpBQU1COEdBMVVkSXdRWU1CYUFGRDh2bENOUjAxREptaWc5N2JCODVjK2xrR0taTUhBR0NDc0dBUVVGQndFQkJHUXdZakF0QmdnckJnRUZCUWN3QW9ZaGFIUjBjRG92TDJObGNuUnpMbUZ3Y0d4bExtTnZiUzkzZDJSeVp6WXVaR1Z5TURFR0NDc0dBUVVGQnpBQmhpVm9kSFJ3T2k4dmIyTnpjQzVoY0hCc1pTNWpiMjB2YjJOemNEQXpMWGQzWkhKbk5qQXlNSUlCSGdZRFZSMGdCSUlCRlRDQ0FSRXdnZ0VOQmdvcWhraUc5Mk5rQlFZQk1JSCtNSUhEQmdnckJnRUZCUWNDQWpDQnRneUJzMUpsYkdsaGJtTmxJRzl1SUhSb2FYTWdZMlZ5ZEdsbWFXTmhkR1VnWW5rZ1lXNTVJSEJoY25SNUlHRnpjM1Z0WlhNZ1lXTmpaWEIwWVc1alpTQnZaaUIwYUdVZ2RHaGxiaUJoY0hCc2FXTmhZbXhsSUhOMFlXNWtZWEprSUhSbGNtMXpJR0Z1WkNCamIyNWthWFJwYjI1eklHOW1JSFZ6WlN3Z1kyVnlkR2xtYVdOaGRHVWdjRzlzYVdONUlHRnVaQ0JqWlhKMGFXWnBZMkYwYVc5dUlIQnlZV04wYVdObElITjBZWFJsYldWdWRITXVNRFlHQ0NzR0FRVUZCd0lCRmlwb2RIUndPaTh2ZDNkM0xtRndjR3hsTG1OdmJTOWpaWEowYVdacFkyRjBaV0YxZEdodmNtbDBlUzh3SFFZRFZSME9CQllFRklGaW9HNHdNTVZBMWt1OXpKbUdOUEFWbjNlcU1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBUUJnb3Foa2lHOTJOa0Jnc0JCQUlGQURBS0JnZ3Foa2pPUFFRREF3TnBBREJtQWpFQStxWG5SRUM3aFhJV1ZMc0x4em5qUnBJelBmN1ZIejlWL0NUbTgrTEpsclFlcG5tY1B2R0xOY1g2WFBubGNnTEFBakVBNUlqTlpLZ2c1cFE3OWtuRjRJYlRYZEt2OHZ1dElETVhEbWpQVlQzZEd2RnRzR1J3WE95d1Iya1pDZFNyZmVvdCIsIk1JSURGakNDQXB5Z0F3SUJBZ0lVSXNHaFJ3cDBjMm52VTRZU3ljYWZQVGp6Yk5jd0NnWUlLb1pJemowRUF3TXdaekViTUJrR0ExVUVBd3dTUVhCd2JHVWdVbTl2ZENCRFFTQXRJRWN6TVNZd0pBWURWUVFMREIxQmNIQnNaU0JEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURVRNQkVHQTFVRUNnd0tRWEJ3YkdVZ1NXNWpMakVMTUFrR0ExVUVCaE1DVlZNd0hoY05NakV3TXpFM01qQXpOekV3V2hjTk16WXdNekU1TURBd01EQXdXakIxTVVRd1FnWURWUVFERER0QmNIQnNaU0JYYjNKc1pIZHBaR1VnUkdWMlpXeHZjR1Z5SUZKbGJHRjBhVzl1Y3lCRFpYSjBhV1pwWTJGMGFXOXVJRUYxZEdodmNtbDBlVEVMTUFrR0ExVUVDd3dDUnpZeEV6QVJCZ05WQkFvTUNrRndjR3hsSUVsdVl5NHhDekFKQmdOVkJBWVRBbFZUTUhZd0VBWUhLb1pJemowQ0FRWUZLNEVFQUNJRFlnQUVic1FLQzk0UHJsV21aWG5YZ3R4emRWSkw4VDBTR1luZ0RSR3BuZ24zTjZQVDhKTUViN0ZEaTRiQm1QaENuWjMvc3E2UEYvY0djS1hXc0w1dk90ZVJoeUo0NXgzQVNQN2NPQithYW85MGZjcHhTdi9FWkZibmlBYk5nWkdoSWhwSW80SDZNSUgzTUJJR0ExVWRFd0VCL3dRSU1BWUJBZjhDQVFBd0h3WURWUjBqQkJnd0ZvQVV1N0Rlb1ZnemlKcWtpcG5ldnIzcnI5ckxKS3N3UmdZSUt3WUJCUVVIQVFFRU9qQTRNRFlHQ0NzR0FRVUZCekFCaGlwb2RIUndPaTh2YjJOemNDNWhjSEJzWlM1amIyMHZiMk56Y0RBekxXRndjR3hsY205dmRHTmhaek13TndZRFZSMGZCREF3TGpBc29DcWdLSVltYUhSMGNEb3ZMMk55YkM1aGNIQnNaUzVqYjIwdllYQndiR1Z5YjI5MFkyRm5NeTVqY213d0hRWURWUjBPQkJZRUZEOHZsQ05SMDFESm1pZzk3YkI4NWMrbGtHS1pNQTRHQTFVZER3RUIvd1FFQXdJQkJqQVFCZ29xaGtpRzkyTmtCZ0lCQkFJRkFEQUtCZ2dxaGtqT1BRUURBd05vQURCbEFqQkFYaFNxNUl5S29nTUNQdHc0OTBCYUI2NzdDYUVHSlh1ZlFCL0VxWkdkNkNTamlDdE9udU1UYlhWWG14eGN4ZmtDTVFEVFNQeGFyWlh2TnJreFUzVGtVTUkzM3l6dkZWVlJUNHd4V0pDOTk0T3NkY1o0K1JHTnNZRHlSNWdtZHIwbkRHZz0iLCJNSUlDUXpDQ0FjbWdBd0lCQWdJSUxjWDhpTkxGUzVVd0NnWUlLb1pJemowRUF3TXdaekViTUJrR0ExVUVBd3dTUVhCd2JHVWdVbTl2ZENCRFFTQXRJRWN6TVNZd0pBWURWUVFMREIxQmNIQnNaU0JEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURVRNQkVHQTFVRUNnd0tRWEJ3YkdVZ1NXNWpMakVMTUFrR0ExVUVCaE1DVlZNd0hoY05NVFF3TkRNd01UZ3hPVEEyV2hjTk16a3dORE13TVRneE9UQTJXakJuTVJzd0dRWURWUVFEREJKQmNIQnNaU0JTYjI5MElFTkJJQzBnUnpNeEpqQWtCZ05WQkFzTUhVRndjR3hsSUVObGNuUnBabWxqWVhScGIyNGdRWFYwYUc5eWFYUjVNUk13RVFZRFZRUUtEQXBCY0hCc1pTQkpibU11TVFzd0NRWURWUVFHRXdKVlV6QjJNQkFHQnlxR1NNNDlBZ0VHQlN1QkJBQWlBMklBQkpqcEx6MUFjcVR0a3lKeWdSTWMzUkNWOGNXalRuSGNGQmJaRHVXbUJTcDNaSHRmVGpqVHV4eEV0WC8xSDdZeVlsM0o2WVJiVHpCUEVWb0EvVmhZREtYMUR5eE5CMGNUZGRxWGw1ZHZNVnp0SzUxN0lEdll1VlRaWHB
|
|||
|
|
// 从 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();
|
|||
|
|
}
|
|||
|
|
}
|