Apple WeatherKit REST API 上手

語言: CN / TW / HK

我正在參加「掘金·啟航計劃」


在 WWDC 2022,Apple 推出了全新的天氣資料服務 WeatherKit ,提供了豐富的天氣資料供開發者使用。

只要你已經購買了Apple的開發者賬號,就可以使用它。

和大多數天氣服務一樣,WeatherKit 也是按請求次數收費的,價格如下:

  • 50 萬次呼叫/月:會員資格已包含
  • 100 萬次呼叫/月:49.99 美元
  • 200 萬次呼叫/月:99.99 美元
  • 500 萬次呼叫/月:249.99 美元
  • 1,000 萬次呼叫/月:499.99 美元
  • 2,000 萬次呼叫/月:999.99 美元

詳情見 Apple官網:開始使用 WeatherKit - Apple Developer

可以看到,開發者預設每月有 50萬次呼叫的額度可以使用,不用額外付費,只有超過這個數量才需要另外付費。

蘋果提供了兩種方式呼叫 WeatherKit:

基於蘋果一貫的習慣,WeatherKit系統庫從 iOS 16 開始支援,並不支援老版本的系統。並且只支援 Swift,不支援OC。

所以,在現階段,對於要支援老版本系統的APP並沒有實用價值。

但是 REST API 就不一樣了,因為它是網路請求介面,它可以支援所有版本的系統,甚至非蘋果生態的 web 和 Windows等平臺也可以使用。

所以,我們今天來看看,如何開始使用WeatherKit REST API。

一、開始之前

1、建立APP ID 並新增WeatherKit 許可權

開始之前需要一些準備工作,開啟蘋果的開發者網站。在 Certificates, Identifiers, and Profiles 頁面選擇 Identifiers,建立一個新 App ID ,如果你不需要建立新的,你也可以選擇已有的 APP ID。然後開啟後在 App Services 一欄勾選 WeatherKit 選項。可能需要等待30分鐘讓蘋果同步許可權之後才可以進行API請求。

image-20221003210130533.png

這裡需要記錄一下 AppID PrefixBundle ID 以及你賬戶的 Team ID。等一下會用到。

2、建立WeatherKit 的金鑰

Certificates, Identifiers, and Profiles 頁面選擇 Keys 選項,然後新建一個Key。勾選WeatherKit 選項。

image-20221003211636293.png

系統會提示你下載一個金鑰檔案,這是你呼叫WeatherKit服務的簽名用的私鑰,只能下載一次,一定要妥善保管到安全的位置,不能洩露。

然後記錄一下 生成的Key的 ID。稍後會用到。

image-20221003211916138.png

二、開始Weather Kit REST API之旅

Weather Kit REST API 使用標準的 JWT( JSON Web Token )進行授權,首先我們要生成授權的 JWT,通常這一步應該在後端伺服器上完成。JWT 標準文件:JWT specification

簡單來說,JWT 是一個字串,由三段base64編碼的字串組成,用英文的句號“.”分隔。

例如: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

第一段和第二段都是JSON經過urlBase64編碼後的字串,這裡使用的base64和普通的有一點區別,是專門為url傳遞優化過的,具體變化如下:

1、去掉了結尾的等號“=”

2、將加號“+”替換成短線“-”

3、將斜槓“/”替換成下劃線“_”

第一段稱為頭部(Header),記錄了一些JWT的格式和簽名方式等,具體可以參考 JWT標準文件。

第二段稱為負載(Payload),是JWT的主體部分,記錄了JWT的關鍵資訊。

第三段是前兩段的數字簽名,是為了防止JWT被偽造和篡改。

WeatherKit 對前兩段的內容和簽名方式都有要求。

1、Header部分

json { "alg": "ES256", "kid": "ABC123DEFG", "id": "DEF123GHIJ.com.example.weatherkit-client" }

其中:

alg :是簽名方式,Apple要求,必須為“ES256”,它表示我們應該使用 SHA256演算法對內容進行雜湊後使用P-256曲線的橢圓曲線數字簽名演算法(ECDSA)對其進行簽名。

具體簽名方式後門再說明。

kid :是剛才第一步中我們建立的Key的ID。

id :是Team ID和bundle ID 的拼接。

Header的json構造完成後使用 urlBase64對其進行編碼,得到了JWT的第一部分:

eyJhbGciOiJFUzI1NiIsImtpZCI6IkFCQzEyM0RFRkciLCJpZCI6IkRFRjEyM0dISUouY29tLmV4YW1wbGUud2VhdGhlcmtpdC1jbGllbnQifQ

2、Payload部分

{ "iss": "DEF123GHIJ", "iat": 1437179036, "exp": 1493298100, "sub": "com.example.weatherkit-client" }

iss:JWT簽發者,值為你的Team ID。

iat:JWT簽發時間,Unix時間戳,單位秒,整數。

exp:JWT過期時間,Unix時間戳,單位秒,整數。

sub:你app的 bundle ID

Payload的json構造完成後同樣使用 urlBase64對其進行編碼,得到了JWT的第二部分:

eyJpc3MiOiJERUYxMjNHSElKIiwiaWF0IjoxNDM3MTc5MDM2LCJleHAiOjE0OTMyOTgxMDAsInN1YiI6ImNvbS5leGFtcGxlLndlYXRoZXJraXQtY2xpZW50In0

3、簽名

下一步,將header和payload的base64字串用“.”拼接在一起,然後對拼接後的字串進行 ECDSA 簽名。

拼接後的字串為:eyJhbGciOiJFUzI1NiIsImtpZCI6IkFCQzEyM0RFRkciLCJpZCI6IkRFRjEyM0dISUouY29tLmV4YW1wbGUud2VhdGhlcmtpdC1jbGllbnQifQ.eyJpc3MiOiJERUYxMjNHSElKIiwiaWF0IjoxNDM3MTc5MDM2LCJleHAiOjE0OTMyOTgxMDAsInN1YiI6ImNvbS5leGFtcGxlLndlYXRoZXJraXQtY2xpZW50In0

JWT的簽發應該放在後端進行,所以這裡以 go 語言為示例:

```go package main

import ( "crypto/ecdsa" "crypto/rand" "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/pem" "fmt" "os" )

func main() { sign := EccSignature("eyJhbGciOiJFUzI1NiIsImtpZCI6IkFCQzEyM0RFRkciLCJpZCI6IkRFRjEyM0dISUouY29tLmV4YW1wbGUud2VhdGhlcmtpdC1jbGllbnQifQ.eyJpc3MiOiJERUYxMjNHSElKIiwiaWF0IjoxNDM3MTc5MDM2LCJleHAiOjE0OTMyOTgxMDAsInN1YiI6ImNvbS5leGFtcGxlLndlYXRoZXJraXQtY2xpZW50In0", "./test.p8") fmt.Printf("sign: %s", sign) }

// textToSign 等待簽名的字串,privateKeyPath 私鑰檔案的路徑 // return: 簽名 func EccSignature(textToSign string, privateKeyPath string) (sign string) { //------1.獲取私匙------ //Step1:開啟檔案獲取原始私匙 file, err := os.Open(privateKeyPath) if err != nil { panic(err) } defer file.Close() fileinfo, err := file.Stat() if err != nil { panic(err) } buf := make([]byte, fileinfo.Size()) file.Read(buf) //Step2:私匙的反pem編碼化 block, _ := pem.Decode(buf) //Step3:私匙的反x509序列化, 對於不同格式的私鑰檔案可以能需要使用不同的 Parse方法,對於WeatherKit提供的.p8檔案,這裡使用ParsePKCS8PrivateKey key, err := x509.ParsePKCS8PrivateKey(block.Bytes) if err != nil { panic(err) } // 轉換型別 privKey := key.(*ecdsa.PrivateKey) //------求取明文的雜湊值------ //Step1:建立基於sha256雜湊函式的hash介面 myHash := sha256.New() //Step2:寫入資料 myHash.Write([]byte(textToSign)) //Step:求出明文的雜湊值 hashText := myHash.Sum(nil) //------對明文的雜湊值進行數字簽名 r, s, err := ecdsa.Sign(rand.Reader, privKey, hashText) if err != nil { panic(err) } //Step2:得到 r和s 的位元組編碼 rText := r.Bytes() if err != nil { panic(err) } sText := s.Bytes() if err != nil { panic(err) } // 使用 r||s 格式輸出 signBytes := append(rText, sText...) // 編碼成 base64 sign = base64.RawURLEncoding.EncodeToString(signBytes) return sign }

```

例如得到簽名字串:f2SAjBpBGyidryrlGyVaso2yLPRufYULjdhI0zcqxB07iN9OI9rvQaHo1xxoTfTD9A6DVpb96sE8leGyTo0dYg

將簽名後得到的base64字元串同樣使用“.”拼接在剛才的字串後門,就完成了JWT的簽發。

得到最終的JWT:

eyJhbGciOiJFUzI1NiIsImtpZCI6IkFCQzEyM0RFRkciLCJpZCI6IkRFRjEyM0dISUouY29tLmV4YW1wbGUud2VhdGhlcmtpdC1jbGllbnQifQ.eyJpc3MiOiJERUYxMjNHSElKIiwiaWF0IjoxNDM3MTc5MDM2LCJleHAiOjE0OTMyOTgxMDAsInN1YiI6ImNvbS5leGFtcGxlLndlYXRoZXJraXQtY2xpZW50In0.f2SAjBpBGyidryrlGyVaso2yLPRufYULjdhI0zcqxB07iN9OI9rvQaHo1xxoTfTD9A6DVpb96sE8leGyTo0dYg

最終得到的JWT格式應該是header.payload.sign這樣的三段結構,你可在 JWT.io 中選擇ES256然後使用他的證書進行簽名後對自己的簽名方法進行驗證(注意:為了安全起見,請不要將自己的私鑰隨便複製到網頁中)。

將它返回給APP或者前端等需要請求WeatherKit REST API 的地方。

4、發起請求

終於,經過前面的準備,我們已經具備了請求Weather Kit REST API的所有前提條件。

發起請求很簡單,就是正常的GET請求,只是需要在HTTP HEADER中加入一個欄位:

Authorization: Bearer [developer token]

將 [developer token] 替換為你簽發的 JWT。

然後根據Weather Kit REST API 文件所描述的路徑和引數發起請求,就能正常得到天氣資料了。

這裡以Postman為例

在 Authorization一欄選擇 Bearer Token 將JWT填入右邊的Token輸入框就可以自動新增到Header中。

你也可以自己手動在 Header引數中新增 Authorization 欄位。

image-20221004005136375.png

發起請求:

image-20221004004815579.png

三、最後

個人 感覺WeatherKit REST API還是很不錯的,不需要額外付費就能有50萬次每月的訪問量,對於小體量的個人開發者完全夠用了。

還有一點值得注意的是,按照蘋果的要求,如果你使用了WeatherKit,你應該在APP中標明資料來源:

image-20221004005914374.png

image-20221004005816516.png


參考: