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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 04:22:56