使用JWT.io生成Apple Music API开发者令牌失败,如何在Xcode中配置?
我之前也遇到过类似的问题——用jwt.io手动生成ES256格式的JWT总是踩坑,后来改用Xcode原生Security框架在Objective-C里实现,反而顺畅多了。下面是完整的配置和代码实现步骤,帮你搞定Apple Music API的令牌生成:
一、先准备好必备信息
首先确保你已经从Apple Developer后台拿到这些关键内容:
- Team ID:在开发者账号的「Membership」页面可以找到
- Key ID:创建Apple Music API专用密钥时生成的ID(创建路径:Certificates, Identifiers & Profiles → Keys → 新建Key,勾选「MusicKit」权限)
- 私钥文件:下载的
.p8格式私钥,记得把它添加到Xcode项目中(建议加到.gitignore里,避免泄露)
二、导入必要的系统框架
在你的Objective-C文件顶部导入Security框架:
#import <Security/Security.h>
三、实现JWT生成核心代码
下面是完整的工具类代码,包含私钥加载、Base64URL编码、ES256签名、JWT拼接四个核心部分:
1. 加载.p8私钥为SecKeyRef
这个方法负责把PEM格式的私钥转换成系统能识别的SecKeyRef:
- (SecKeyRef)loadPrivateKeyFromP8File:(NSString *)filePath { NSString *pemString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; // 去除PEM格式的头尾标记和换行符 NSString *cleanedKey = [pemString stringByReplacingOccurrencesOfString:@"-----BEGIN PRIVATE KEY-----" withString:@""]; cleanedKey = [cleanedKey stringByReplacingOccurrencesOfString:@"-----END PRIVATE KEY-----" withString:@""]; cleanedKey = [cleanedKey stringByReplacingOccurrencesOfString:@"\n" withString:@""]; // 转成Data NSData *keyData = [[NSData alloc] initWithBase64EncodedString:cleanedKey options:NSDataBase64DecodingIgnoreUnknownCharacters]; // 配置密钥参数 NSDictionary *attributes = @{ (__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeEC, (__bridge id)kSecAttrKeyClass: (__bridge id)kSecAttrKeyClassPrivate, (__bridge id)kSecAttrKeySizeInBits: @256, (__bridge id)kSecAttrKeyExchangeAlgorithm: (__bridge id)kSecKeyAlgorithmECDH }; SecKeyRef privateKey = NULL; OSStatus status = SecKeyCreateWithData((__bridge CFDataRef)keyData, (__bridge CFDictionaryRef)attributes, &privateKey); if (status != errSecSuccess) { NSLog(@"私钥加载失败,错误码:%d", (int)status); return NULL; } return privateKey; }
2. 实现Base64URL编码(区别于普通Base64)
Apple要求JWT的Header和Payload必须用Base64URL编码,需要替换特殊字符并去掉末尾的=:
- (NSString *)base64URLEncode:(NSData *)data { NSString *base64 = [data base64EncodedStringWithOptions:0]; // 替换Base64字符为URL安全字符 base64 = [base64 stringByReplacingOccurrencesOfString:@"+" withString:@"-"]; base64 = [base64 stringByReplacingOccurrencesOfString:@"/" withString:@"_"]; // 去掉末尾的填充符= base64 = [base64 stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"="]]; return base64; }
3. 生成完整的JWT令牌
这个方法整合所有步骤,生成符合Apple要求的ES256格式JWT:
- (NSString *)generateAppleMusicJWTWithTeamID:(NSString *)teamID keyID:(NSString *)keyID privateKeyPath:(NSString *)privateKeyPath { SecKeyRef privateKey = [self loadPrivateKeyFromP8File:privateKeyPath]; if (!privateKey) { return nil; } // 1. 构建Header:指定算法为ES256,类型为JWT NSDictionary *header = @{ @"alg": @"ES256", @"typ": @"JWT", @"kid": keyID }; NSData *headerData = [NSJSONSerialization dataWithJSONObject:header options:0 error:nil]; NSString *headerBase64URL = [self base64URLEncode:headerData]; // 2. 构建Payload:iss是TeamID,iat是当前时间戳,exp是过期时间(最多180天,即6个月) NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970]; NSDictionary *payload = @{ @"iss": teamID, @"iat": @((NSInteger)currentTime), @"exp": @((NSInteger)(currentTime + 180*24*3600)) // 180天后过期 }; NSData *payloadData = [NSJSONSerialization dataWithJSONObject:payload options:0 error:nil]; NSString *payloadBase64URL = [self base64URLEncode:payloadData]; // 3. 拼接Header.Payload NSString *message = [NSString stringWithFormat:@"%@.%@", headerBase64URL, payloadBase64URL]; NSData *messageData = [message dataUsingEncoding:NSUTF8StringEncoding]; // 4. 用ES256算法签名 SecKeyAlgorithm algorithm = kSecKeyAlgorithmECDSASignatureMessageX962SHA256; NSData *signatureData = NULL; if (@available(iOS 10.0, *)) { OSStatus status = SecKeyCreateSignature(privateKey, algorithm, (__bridge CFDataRef)messageData, (CFErrorRef *)&signatureData); if (status != errSecSuccess) { NSLog(@"签名失败,错误码:%d", (int)status); CFRelease(privateKey); return nil; } } else { // iOS 10以下的兼容处理(如果需要),不过Apple Music API要求iOS 11+,可以忽略 NSLog(@"系统版本过低,不支持ES256签名"); CFRelease(privateKey); return nil; } // 5. 把签名转成Base64URL,拼接成最终JWT NSString *signatureBase64URL = [self base64URLEncode:signatureData]; NSString *jwt = [NSString stringWithFormat:@"%@.%@.%@", headerBase64URL, payloadBase64URL, signatureBase64URL]; CFRelease(privateKey); return jwt; }
四、调用示例
在需要生成令牌的地方调用这个方法:
// 替换成你的Team ID、Key ID和私钥文件路径 NSString *teamID = @"YOUR_TEAM_ID"; NSString *keyID = @"YOUR_KEY_ID"; NSString *privateKeyPath = [[NSBundle mainBundle] pathForResource:@"AuthKey" ofType:@"p8"]; NSString *jwtToken = [self generateAppleMusicJWTWithTeamID:teamID keyID:keyID privateKeyPath:privateKeyPath]; NSLog(@"生成的Apple Music JWT:%@", jwtToken);
五、常见问题排查(你用jwt.io失败的可能原因)
- 私钥格式错误:jwt.io要求私钥是PEM格式,要确保你粘贴的是完整的
-----BEGIN PRIVATE KEY-----到-----END PRIVATE KEY-----内容,包括换行 - Payload字段错误:
iss必须是你的Team ID,exp不能超过当前时间+180天,iat必须是当前时间戳 - Base64编码差异:手动处理Base64时容易忽略URL安全转换,jwt.io的Base64URL选项要勾选上
- 算法选择错误:一定要在jwt.io的算法下拉框选
ES256,而不是默认的HS256
内容的提问来源于stack exchange,提问作者Stasy Meclazcke
相关产品推荐
相关产品推荐

