求助:用Java解码苹果App Store API返回的JWSTransaction遇报错
解码苹果App Store API返回的JWSTransaction Java代码修正
错误原因
你犯了两个核心错误:
- 密钥类型错误:验证苹果签名的JWT时,不能用你自己的P8私钥。苹果的JWSTransaction是苹果用他们的RSA私钥签名的,我们需要用苹果官方发布的公钥来验证签名,而不是你用来向苹果API发起请求的私钥。
- 算法不匹配:苹果的JWSTransaction使用的签名算法是
RS256,但你代码里用了EC(椭圆曲线)密钥,这就导致了算法不兼容的错误。
正确解决方案
要解码并验证苹果的JWSTransaction,步骤如下:
- 从苹果官方的JWT公钥端点(地址为https://appleid.apple.com/auth/keys)获取公钥集合
- 根据JWT头部的
kid字段,匹配对应的公钥 - 使用该公钥验证JWT签名并解析内容
以下是基于JJWT库的可行代码:
依赖(Maven)
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency>
解码验证代码
import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; import java.net.URL; import java.nio.charset.StandardCharsets; import java.security.PublicKey; import java.util.List; import java.util.Map; public class AppleJwtDecoder { private static final String APPLE_PUBLIC_KEYS_URL = "https://appleid.apple.com/auth/keys"; private List<Map<String, Object>> applePublicKeys; public AppleJwtDecoder() { // 初始化时获取苹果公钥,实际项目建议缓存(比如每24小时刷新一次),避免重复请求 try { String keysJson = new String( new URL(APPLE_PUBLIC_KEYS_URL).openStream().readAllBytes(), StandardCharsets.UTF_8 ); applePublicKeys = (List<Map<String, Object>>) Jwts.parser().parse(keysJson).getBody().get("keys"); } catch (Exception e) { e.printStackTrace(); } } public void decodeTransactionJwt(String transactionJwt) { try { // 先解析JWT头部获取kid字段 JwtHeader header = Jwts.parser().parseClaimsJws(transactionJwt).getHeader(); String kid = header.getKeyId(); // 根据kid匹配对应的苹果公钥 PublicKey publicKey = findPublicKeyByKid(kid); if (publicKey == null) { System.out.println("未找到匹配的苹果公钥"); return; } // 验证签名并解析JWT内容 Jws<Claims> jws = Jwts.parserBuilder() .setSigningKey(publicKey) .build() .parseClaimsJws(transactionJwt); // 输出解析后的交易信息,可根据需求获取字段 Claims claims = jws.getBody(); System.out.println("解析成功:"); System.out.println("transactionId: " + claims.get("transactionId")); System.out.println("productId: " + claims.get("productId")); System.out.println("purchaseDate: " + claims.get("purchaseDate")); } catch (JwtException e) { System.out.println("JWT验证失败:" + e.getMessage()); } catch (Exception e) { System.out.println("解码出错:" + e.getMessage()); } } private PublicKey findPublicKeyByKid(String kid) { for (Map<String, Object> keyMap : applePublicKeys) { if (kid.equals(keyMap.get("kid"))) { // 从公钥参数生成RSA公钥 String modulus = (String) keyMap.get("n"); String exponent = (String) keyMap.get("e"); return Keys.createRsaPublicKeyFromModulusAndExponent( Keys.base64UrlDecode(modulus), Keys.base64UrlDecode(exponent) ); } } return null; } // 测试入口 public static void main(String[] args) { AppleJwtDecoder decoder = new AppleJwtDecoder(); String transactionJwt = "xxx.yyy.zzz"; // 替换为你的JWSTransaction字符串 decoder.decodeTransactionJwt(transactionJwt); } }
关键注意点
- 公钥缓存:苹果的公钥不会频繁变更,实际项目中建议缓存这些公钥,避免每次解码都请求苹果的公钥端点,提升性能并减少网络依赖。
- 字段扩展:JWSTransaction包含很多交易相关字段,可根据业务需求从
Claims对象中获取对应值。 - 异常处理:实际生产环境中需完善异常捕获逻辑,比如网络请求失败、公钥解析失败等场景。
内容的提问来源于stack exchange,提问作者Mike Dee
相关产品推荐
相关产品推荐

