如何调用 Apple Music API 并避免 401

Posted

技术标签:

【中文标题】如何调用 Apple Music API 并避免 401【英文标题】:How to call Apple Music API and avoid 401 【发布时间】:2018-07-18 08:49:36 【问题描述】:

我在尝试调用 Apple Music API 时不断收到 401 错误。我使用的是 Windows 机器,下面是它的 Python 代码。


import datetime
import jwt

secret = """-----BEGIN PRIVATE KEY-----
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxx
-----END PRIVATE KEY-----"""

kid = 'ABCDEFGHI'
teamId = 'JKLMNOPQRS'
alg = 'ES256'

headers = 
    'alg': alg,
    'kid': kid


payload = 
    'iss': teamId,
    'iat': 1518033023,
    'exp': 1518290267


if __name__ == '__main__':
    '''Create an auth token'''
    token = jwt.encode(payload, secret, algorithm=alg, headers=headers)

print '----CURL----'
print ("curl -v -H 'Authorization: Bearer %s' \"https://api.music.apple.com/v1/catalog/us/playlists/pl.14362d3dfe4b41f7878939782647e0ba\" " % (token))

【问题讨论】:

【参考方案1】:

Apple Music API 需要开发者令牌和用户令牌。虽然问题是关于 python 的,但这个答案是为将来遇到这个问题的 swift 开发人员准备的。

例如,

static func addPlaylistPathURLString(developerToken: String, userToken: String) -> URLRequest 
         var urlComponents = URLComponents()
         urlComponents.scheme = "https"
         urlComponents.host = "api.music.apple.com"
         urlComponents.path = "/v1/me/library/playlists/

         // Create and configure the `URLRequest`.

         var urlRequest = URLRequest(url: urlComponents.url!)
         urlRequest.httpMethod = "GET"

         urlRequest.addValue("Bearer \(developerToken)", forHTTPHeaderField: "Authorization")
         urlRequest.addValue(userToken, forHTTPHeaderField: "Music-User-Token")

         return urlRequest
     

     func addPlaylistPathURLString(with developertoken: String, userToken: String, completion: @escaping CatalogSearchCompletionHandler) 

 guard let developerToken = refreshDeveloperToken() // its developer token 
else 
           fatalError("Developer Token not configured. See README for more details.")
             
            let urlRequest = AppleMusicRequestFactory.addPlaylistPathURLString(developerToken: developerToken, userToken: userToken)
            print(urlRequest)

             let task = urlSession.dataTask(with: urlRequest)  (data, response, error) in
                 guard error == nil, let urlResponse = response as? HTTPURLResponse, urlResponse.statusCode == 200 else 
                     completion([], error)

                     return
                 
                 guard
                    let data = data,
                   let json = String(data: data, encoding: .utf8)

                else  return 
                print("json:", json)
               do 
                    // do your stuffs---

                   catch 

                 print("An error occurred: \(error.localizedDescription)")
                 
             

             task.resume()

        

关于如何从开发者令牌中获取用户令牌,

func requestUserToken() 
        guard let developerToken = appleMusicManager.refreshDeveloperToken() // its developer token 
   else 
            return
        

        if SKCloudServiceController.authorizationStatus() == .authorized 

            let completionHandler: (String?, Error?) -> Void =  [weak self] (token, error) in
                guard error == nil else 
                    print("An error occurred when requesting user token: \(error!.localizedDescription)")
                    return
                

                guard let token = token else 
                    print("Unexpected value from SKCloudServiceController for user token.")
                    return
                

                self?.userToken = token
                print(token)

                /// Store the Music User Token for future use in your application.
                let userDefaults = UserDefaults.standard

                userDefaults.set(token, forKey: AuthorizationManager.userTokenUserDefaultsKey)
                userDefaults.synchronize()

                if self?.cloudServiceStorefrontCountryCode == "" 
                    self?.requestStorefrontCountryCode()
                

                NotificationCenter.default.post(name: AuthorizationManager.cloudServiceDidUpdateNotification, object: nil)
            

            if #available(ios 11.0, *) 
                cloudServiceController.requestUserToken(forDeveloperToken: developerToken, completionHandler: completionHandler)
             else 
                cloudServiceController.requestPersonalizationToken(forClientToken: developerToken, withCompletionHandler: completionHandler)
            
        
    

【讨论】:

【参考方案2】:

我遇到了同样的问题,对我来说,我的 .p8 文件在多行上却有键:

-----BEGIN PRIVATE KEY-----
notarealkey5GSM49AgEGCCqGSM49AwEHBHkwdwIBAQQguWRXMHYkuFImkMGByqEPT
jaXQyO0WK1BjYpuDxIgNQ5nHRRFCuUOi8mgCgYIKoZIzj0DAQehcp0+Z+jwRANCAA
RCBFg8fL08QS36Fb8HmY+eFrDWMO00w5unCo5n8VyLhvttIZeByXlVsJrK/L3f/
F2wYmZme
-----END PRIVATE KEY-----

对比:

-----BEGIN PRIVATE KEY-----
asdfg1rty5GSM49AgEGCCqGSM49AwEHBHkwdwI...
-----END PRIVATE KEY-----

【讨论】:

【参考方案3】:

字符串格式化操作符也有同样的问题。因此使用这个:

"""Create an auth token"""
token = jwt.encode(payload, secret, algorithm=alg, headers=headers)
token_str = token.decode('utf-8')  # converts bytes to string

url = "https://api.music.apple.com/v1/catalog/us/playlists/pl.14362d3dfe4b41f7878939782647e0ba" 

print("curl -v -H 'Authorization: Bearer" + token_str + "\"" + url + "\"")

进一步:我假设您想直接访问数据。为此,您可以使用requests 库:

import requests
request_obj = requests.get(url, headers='Authorization': "Bearer " + token_str)
json_dict = request_obj.json()

干杯!

【讨论】:

请求库的链接失效了能不能更新一下!【参考方案4】:

添加到 sanwall 的答案 - 这对我有用(Python 3.x):

from datetime import datetime
import jwt
import requests
import json

secret_key = '''-----BEGIN PRIVATE KEY-----
... your secret key here ...
-----END PRIVATE KEY-----'''

key_id  = '0123456789' # <-- your key id here
team_id = '0123456789' # <-- your team id here
alg     = 'ES256'
iat     = int(datetime.utcnow().strftime("%s")) # set issued at to now
exp     = iat + 86400 # add e.g. 24h from now for expiration (24 * 3600secs == 86400)

payload = 
    'iss': team_id,
    'iat': iat,
    'exp': exp


headers = 
    'alg': alg,
    'kid': key_id


token = jwt.encode(payload, secret_key, algorithm=alg, headers=headers)
token_str = token.decode('utf-8')

url = "https://api.music.apple.com/v1/catalog/us/songs/203709340"
print (f"curl -v -H 'Authorization: Bearer token_str' url")

或者使用请求生成的令牌字符串:

res = requests.get(url, headers='Authorization': "Bearer " + token_str)
result = json.loads(res.text)

打印输出:

'data': ['id': '203709340', 'type': 'songs', 'href': '/v1/catalog/us/songs/203709340', ... etc ...

【讨论】:

以上是关于如何调用 Apple Music API 并避免 401的主要内容,如果未能解决你的问题,请参考以下文章

获取库播放列表艺术品 Apple Music API

Apple Music API 获取当前播放的歌曲

为 Apple Music API 创建开发者令牌

如何在我自己的应用程序中实现 Apple Music 中的迷你播放器?

是否还有办法在 iTunes Store 而不是 Apple Music 中打开 iTunes 搜索 API trackViewUrl?

iOS 如何让 UISlider 和 UIButton 像 Apple 的 Music 应用一样?