refactor(service): 改用JWS验签,移除旧收据解析

废弃ReceiptUtility与AppStoreServerAPIClient,直接以SignedDataVerifier校验客户端传来的signedPayload(JWS),简化流程并减少一次网络IO。
This commit is contained in:
2025-12-16 15:06:41 +08:00
parent c305dfaae4
commit c54c14de58
3 changed files with 33 additions and 72 deletions

View File

@@ -62,12 +62,12 @@ public class AppleReceiptController {
if (body == null) { if (body == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "body 不能为空"); throw new BusinessException(ErrorCode.PARAMS_ERROR, "body 不能为空");
} }
String receipt = body.get("receipt"); String signedPayload = body.get("signedPayload");
if (receipt == null || receipt.isBlank()) { if (signedPayload == null || signedPayload.isBlank()) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "receipt 不能为空"); throw new BusinessException(ErrorCode.PARAMS_ERROR, "signedPayload 不能为空");
} }
Long userId = StpUtil.getLoginIdAsLong(); Long userId = StpUtil.getLoginIdAsLong();
AppleReceiptValidationResult validationResult = appleReceiptService.validateReceipt(receipt); AppleReceiptValidationResult validationResult = appleReceiptService.validateReceipt(signedPayload);
applePurchaseService.processPurchase(userId, validationResult); applePurchaseService.processPurchase(userId, validationResult);
return ResultUtils.success(Boolean.TRUE); return ResultUtils.success(Boolean.TRUE);
} }

View File

@@ -7,7 +7,7 @@ public interface AppleReceiptService {
/** /**
* 验证 base64 app receipt 是否有效,并返回解析结果。 * 验证 base64 app receipt 是否有效,并返回解析结果。
* *
* @param appReceipt Base64 的 app receipt以 MI... 开头那串) * @param signedPayload Base64 的 app receipt以 MI... 开头那串)
*/ */
AppleReceiptValidationResult validateReceipt(String appReceipt); AppleReceiptValidationResult validateReceipt(String signedPayload);
} }

View File

@@ -1,11 +1,7 @@
package com.yolo.keyborad.service.impl; package com.yolo.keyborad.service.impl;
import com.apple.itunes.storekit.client.APIException;
import com.apple.itunes.storekit.client.AppStoreServerAPIClient;
import com.apple.itunes.storekit.migration.ReceiptUtility;
import com.apple.itunes.storekit.model.Environment; import com.apple.itunes.storekit.model.Environment;
import com.apple.itunes.storekit.model.JWSTransactionDecodedPayload; import com.apple.itunes.storekit.model.JWSTransactionDecodedPayload;
import com.apple.itunes.storekit.model.TransactionInfoResponse;
import com.apple.itunes.storekit.verification.SignedDataVerifier; import com.apple.itunes.storekit.verification.SignedDataVerifier;
import com.apple.itunes.storekit.verification.VerificationException; import com.apple.itunes.storekit.verification.VerificationException;
import com.yolo.keyborad.model.dto.AppleReceiptValidationResult; import com.yolo.keyborad.model.dto.AppleReceiptValidationResult;
@@ -27,102 +23,67 @@ import java.util.List;
@Service @Service
public class AppleReceiptServiceImpl implements AppleReceiptService { public class AppleReceiptServiceImpl implements AppleReceiptService {
/**
* App Store 服务器 API 客户端
* <p>
* 用于调用 Apple App Store Server API 获取交易信息
* </p>
*/
private final AppStoreServerAPIClient client;
/** /**
* 签名数据验证器 * 签名数据验证器
* <p> * <p>
* 用于验证和解码 Apple 返回的 JWS 签名数据 * 用于验证和解码客户端传来的 JWS 签名交易数据
* </p> * </p>
*/ */
private final SignedDataVerifier signedDataVerifier; private final SignedDataVerifier signedDataVerifier;
/**
* 收据工具类
* <p>
* 用于解析应用收据内容,提取交易 ID 等信息
* </p>
*/
private final ReceiptUtility receiptUtility;
/** /**
* 构造函数 * 构造函数
* <p> * <p>
* 通过构造函数注入所需的依赖组件 * 通过构造函数注入签名数据验证器
* </p> * </p>
* @param client App Store 服务器 API 客户端
* @param signedDataVerifier 签名数据验证器 * @param signedDataVerifier 签名数据验证器
* @param receiptUtility 收据工具类
*/ */
public AppleReceiptServiceImpl(AppStoreServerAPIClient client, public AppleReceiptServiceImpl(SignedDataVerifier signedDataVerifier) {
SignedDataVerifier signedDataVerifier,
ReceiptUtility receiptUtility) {
this.client = client;
this.signedDataVerifier = signedDataVerifier; this.signedDataVerifier = signedDataVerifier;
this.receiptUtility = receiptUtility;
} }
/** /**
* 验证 Apple 应用内购买收据 * 验证 Apple 应用内购买交易
* <p> * <p>
* 执行完整的收据验证流程,包括解析收据、获取交易信息、验证签名和业务逻辑校验 * 直接验证从客户端传来的 JWS (JSON Web Signature) 交易数据
* 使用 SignedDataVerifier 验证签名并解码交易信息,然后执行业务逻辑校验
* </p> * </p>
* @param appReceipt Base64 编码的应用内购买收 * @param signedTransaction 客户端传来的 JWS 格式的签名交易数
* @return 验证结果对象,包含验证状态和交易信息 * @return 验证结果对象,包含验证状态和交易信息
*/ */
@Override @Override
public AppleReceiptValidationResult validateReceipt(String appReceipt) { public AppleReceiptValidationResult validateReceipt(String signedTransaction) {
// 检查收据是否为空 // 检查 JWS 是否为空
if (appReceipt == null || appReceipt.isBlank()) { if (signedTransaction == null || signedTransaction.isBlank()) {
return invalid("empty_receipt"); return invalid("empty_transaction");
} }
try { try {
// 1. 从收据里解析出 transactionId不做验证只是解析 ASN.1 // 1. 使用 SignedDataVerifier 直接验证 JWS 并解码交易 payload
String transactionId = receiptUtility.extractTransactionIdFromAppReceipt(appReceipt); // 这会验证签名、证书链、bundle ID、环境等
// todo 验证服务器传输的transactionId JWSTransactionDecodedPayload payload =
// String transactionId = receiptUtility.extractTransactionIdFromTransactionReceipt(appReceipt); signedDataVerifier.verifyAndDecodeTransaction(signedTransaction);
if (transactionId == null) {
return invalid("no_transaction_id_in_receipt");
}
// 2. 调用 App Store Server API 获取单笔交易信息 // 2. 记录交易信息用于调试
TransactionInfoResponse infoResponse = client.getTransactionInfo(transactionId); log.info("Verified transaction: transactionId={}, productId={}, purchaseDate={}",
payload.getTransactionId(),
payload.getProductId(),
payload.getPurchaseDate());
String signedTransactionInfo = infoResponse.getSignedTransactionInfo(); // 3. 执行业务校验:检查交易是否仍然有效
if (signedTransactionInfo == null) {
return invalid("no_signed_transaction_info");
}
// 3. 使用 SignedDataVerifier 验证 JWS 并解码 payload
JWSTransactionDecodedPayload payload =
signedDataVerifier.verifyAndDecodeTransaction(signedTransactionInfo);
// 4. 执行业务校验:检查交易是否仍然有效
boolean stillActive = isTransactionActive(payload); boolean stillActive = isTransactionActive(payload);
// 构建并返回验证结果 // 4. 构建并返回验证结果
return getAppleReceiptValidationResult(stillActive, payload); return getAppleReceiptValidationResult(stillActive, payload);
} catch (VerificationException e) { } catch (VerificationException e) {
// 验证异常处理 // 验证异常处理签名无效、证书链问题、bundle ID 不匹配等
log.warn("Apple receipt verification failed", e); log.warn("Apple transaction verification failed: status={}", e.getStatus(), e);
return invalid("verification_exception:" + e.getStatus()); return invalid("verification_exception:" + e.getStatus());
} catch (APIException e) {
// API 异常处理,可以根据错误码进一步处理(如环境切换)
// 4040010 表示订单不存在,可能是环境不匹配导致
long code = e.getApiError().errorCode();
log.warn("App Store API error, code={}", code, e);
return invalid("api_exception:" + code);
} catch (Exception e) { } catch (Exception e) {
// 其他未预期异常处理 // 其他未预期异常处理
log.error("Unexpected error when validating Apple receipt", e); log.error("Unexpected error when validating Apple transaction", e);
return invalid("unexpected_error"); return invalid("unexpected_error:" + e.getMessage());
} }
} }