You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django序列化器如何优雅解析App Store Server Notifications V2的JWS数据

实现方案

1. 安装依赖

你需要提前安装JWS处理依赖:

pip install python-jose[cryptography]

2. 实现通用JWS解码工具

先写一个通用的Apple JWS解码函数,统一处理签名验证和解码逻辑,支持缓存Apple公钥避免重复请求:

from jose import jwt, jws
from jose.exceptions import JWSVerificationError
import requests

# 缓存Apple公钥,有效期可自行设置为1天
APPLE_PUBLIC_KEYS = None
APPLE_PUBLIC_KEY_URL = "https://appleid.apple.com/auth/keys"

def get_apple_public_keys():
    global APPLE_PUBLIC_KEYS
    if not APPLE_PUBLIC_KEYS:
        resp = requests.get(APPLE_PUBLIC_KEY_URL)
        resp.raise_for_status()
        APPLE_PUBLIC_KEYS = resp.json()["keys"]
    return APPLE_PUBLIC_KEYS

def decode_appstore_jws(jws_str: str) -> dict:
    # 获取JWS头中的kid,匹配对应Apple公钥
    header = jws.get_unverified_header(jws_str)
    kid = header["kid"]
    public_keys = get_apple_public_keys()
    target_key = next((k for k in public_keys if k["kid"] == kid), None)
    if not target_key:
        raise ValueError(f"未找到匹配的Apple公钥,kid: {kid}")
    # 验证签名并解码payload
    try:
        payload = jwt.decode(
            jws_str, 
            target_key, 
            algorithms=["ES256"], 
            options={"verify_aud": False}
        )
        return payload
    except JWSVerificationError:
        raise ValueError("JWS签名验证失败,请求可能为伪造")

3. 自定义DRF JWS序列化字段

扩展DRF的基础字段,实现自动解码JWS串的能力,无需在业务逻辑中手动处理:

from rest_framework import serializers
from .utils import decode_appstore_jws

class JWSDecoderField(serializers.Field):
    def to_internal_value(self, data):
        if not isinstance(data, str):
            raise serializers.ValidationError("JWS字段必须为字符串格式")
        try:
            return decode_appstore_jws(data)
        except Exception as e:
            raise serializers.ValidationError(f"JWS解码失败: {str(e)}")

4. 编写分层序列化器

按照解码后的结构化数据层级,编写对应的序列化器,嵌套的JWS字段直接使用上面自定义的JWSDecoderField即可:

# 续购信息解码后的序列化器
class RenewalInfoSerializer(serializers.Serializer):
    autoRenewProductId = serializers.CharField()
    autoRenewStatus = serializers.IntegerField()
    expirationIntent = serializers.IntegerField()
    gracePeriodExpiresDate = serializers.IntegerField()
    isInBillingRetryPeriod = serializers.BooleanField()
    # 其他字段按需补充

# 交易信息解码后的序列化器
class TransactionInfoSerializer(serializers.Serializer):
    appAccountToken = serializers.CharField()
    bundleId = serializers.CharField()
    expiresDate = serializers.IntegerField()
    inAppOwnershipType = serializers.CharField()
    isUpgraded = serializers.BooleanField()
    # 其他字段按需补充

# 通知data部分序列化器
class NotificationDataSerializer(serializers.Serializer):
    appAppleId = serializers.IntegerField()
    bundleId = serializers.CharField()
    bundleVersion = serializers.CharField()
    environment = serializers.CharField()
    # 嵌套JWS字段直接使用自定义字段,自动完成解码
    signedRenewalInfo = JWSDecoderField(child=RenewalInfoSerializer())
    signedTransactionInfo = JWSDecoderField(child=TransactionInfoSerializer())

# 最外层通知序列化器
class AppStoreNotificationSerializer(serializers.Serializer):
    notificationType = serializers.CharField()
    subtype = serializers.CharField(required=False, allow_null=True)
    notificationUUID = serializers.UUIDField()
    data = NotificationDataSerializer()

# 如果需要直接存入数据库,改为继承ModelSerializer,关联对应模型即可

5. 视图层实现

完全遵循DRF的常规开发流程,几乎不需要额外改造:

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .serializers import AppStoreNotificationSerializer
from .utils import decode_appstore_jws
from .models import AppStoreNotification

class AppStoreNotificationView(APIView):
    # App Store官方通知不需要鉴权,按需关闭即可
    authentication_classes = []
    permission_classes = []

    def post(self, request):
        # App Store请求的body是raw JWS串,Content-Type为application/jwt
        raw_jws = request.body.decode()
        # 先解码最外层JWS
        try:
            outer_payload = decode_appstore_jws(raw_jws)
        except Exception as e:
            return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
        # 常规序列化校验
        serializer = AppStoreNotificationSerializer(data=outer_payload)
        if not serializer.is_valid():
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
        # 直接保存到数据库即可,所有嵌套字段已经完成解码
        AppStoreNotification.objects.create(**serializer.validated_data)
        # 必须返回200状态码,否则App Store会重复推送通知
        return Response(status=status.HTTP_200_OK)

注意事项

  • 生产环境必须开启签名验证,禁止使用无验证的解码方式,避免恶意伪造请求造成业务损失
  • Apple公钥可设置缓存时间(建议1天),减少不必要的外部请求,提升接口响应速度
  • 字段可根据Apple官方文档按需补充,不需要的字段无需在序列化器中声明,会自动忽略

内容的提问来源于stack exchange,提问作者Nikos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 15:42:04