获取Apple公钥时遭遇401 Unauthorized错误求助
执行代码后得到以下错误输出:
In [14]: print(f"Error fetching public keys: {response.status_code} {response.text}") Error fetching public keys: 401 Unauthenticated
已确认以下信息无误:
- Key ID、Issuer ID和私钥文件均正确
- 私钥拥有管理员权限
- 服务器时间已正确设置为UTC
尝试了两种生成Apple Developer Token的实现方式,且两种方式生成的Token在jwt.io验证均显示正确,但认证仍失败,寻求排查建议。
第一种手动实现代码
def generate_apple_developer_token(): # Load the private key in PEM format with open(PRIVATE_KEY_FILE, 'rb') as key_file: private_key = serialization.load_pem_private_key( key_file.read(), password=None, backend=default_backend() ) # JWT header headers = { "alg": "ES256", # Algorithm: Elliptic Curve "kid": KEY_ID, # Key ID from Apple Developer "typ": "JWT" # Type: JWT } # JWT payload payload = { "iss": ISSUER_ID, # Issuer ID from Apple Developer "iat": int(datetime.utcnow().timestamp()), # Issued at time "exp": int((datetime.utcnow() + timedelta(minutes=10)).timestamp()), # Expiration (max 10 minutes) "aud": "appstoreconnect-v1", # Audience } # Encode the header and payload as base64 header_base64 = base64.urlsafe_b64encode(json.dumps(headers).encode()).decode().rstrip("=") payload_base64 = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip("=") # Concatenate header and payload message = f"{header_base64}.{payload_base64}".encode() # Sign the message using ECDSA with SHA256 signature = private_key.sign( message, ec.ECDSA(hashes.SHA256()) ) # Convert the DER-encoded signature to raw format (r and s concatenated) der_to_raw_ecdsa_format = lambda der: der[4:36] + der[-32:] # Convert the signature to raw format (64 bytes) signature_64 = der_to_raw_ecdsa_format(signature) # Base64 URL-encode the signature signature_base64 = base64.urlsafe_b64encode(signature_64).decode().rstrip("=") # Concatenate header, payload, and signature to form the JWT jwt_token = f"{header_base64}.{payload_base64}.{signature_base64}" return jwt_token def get_apple_public_keys(): try: # Generate a fresh JWT developer_token = generate_apple_developer_token() # Set up headers with the authorization token headers = { "Authorization": f"Bearer {developer_token}" } # Fetch the public keys from Apple response = requests.get('https://api.storekit.itunes.apple.com/in-app-purchase/publicKeys', headers=headers) # Log the response if it's not successful if response.status_code != 200: print(f"Error fetching public keys: {response.status_code} {response.text}") response.raise_for_status() # Raises an exception for 4xx/5xx errors # Parse and return the public keys response_data = response.json() keys = response_data.get('keys') if not keys: print("No 'keys' found in the response from Apple.") return [] return keys except requests.exceptions.RequestException as e: print(f"Error fetching Apple's public keys: {e}") return []
第二种使用jwt库的实现代码
def generate_apple_developer_token(): with open(PRIVATE_KEY_FILE, 'r') as f: private_key = f.read().strip() print('this is the key', private_key) # JWT header headers = { "alg": "ES256", # Apple uses ES256 (Elliptic Curve) "kid": KEY_ID, "typ": "JWT" } # JWT payload payload = { "iss": ISSUER_ID, "iat": int(datetime.utcnow().timestamp()), # Issued at time "exp": int((datetime.utcnow() + timedelta(minutes=10)).timestamp()), # Expiration (max 10 minutes) "aud": "appstoreconnect-v1", } # Generate and return the JWT return jwt.encode(payload, private_key, algorithm="ES256", headers=headers)
排查建议
- 核对接口的认证要求:StoreKit的公钥接口(
https://api.storekit.itunes.apple.com/in-app-purchase/publicKeys)部分场景下无需携带开发者Token,尝试移除Authorization请求头后重新请求,看是否能正常返回数据。 - 检查Token的受众(aud字段):App Store Connect API的
aud为appstoreconnect-v1,但StoreKit相关接口可能需要不同的受众值,确认当前调用接口对应的正确aud参数。 - 验证私钥格式完整性:确保私钥文件包含完整的
-----BEGIN PRIVATE KEY-----和-----END PRIVATE KEY-----标记,无多余空格、换行或字符。 - 确认时间戳准确性:打印生成的
iat和exp时间戳,验证是否与当前UTC时间一致,避免因代码时区处理隐性问题导致时间偏差。 - 检查私钥权限范围:在Apple Developer后台确认私钥关联的API权限包含“In-App Purchase”相关权限,仅管理员权限不足以覆盖所有API操作。
- 排查网络环境:确认服务器无代理、防火墙干扰,请求头未被篡改,可通过抓包工具验证发送的
Authorization头是否完整。 - 重新生成私钥:若以上均无问题,尝试在Apple Developer后台生成新的Key ID和私钥,替换后重新测试。
内容的提问来源于stack exchange,提问作者Lucia
相关产品推荐
相关产品推荐

