Apple WeatherKit REST API 上手
我正在參加「掘金·啟航計劃」
在 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(僅支援Apple平臺,
iOS 16.0+
,iPadOS 16.0+
,macOS 13.0+
,Mac Catalyst 16.0+
,tvOS 16.0+
,watchOS 9.0+
) - Weather KIT REST API(全平臺支援)
基於蘋果一貫的習慣,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請求。
這裡需要記錄一下 AppID Prefix 和 Bundle ID 以及你賬戶的 Team ID。等一下會用到。
2、建立WeatherKit 的金鑰
在 Certificates, Identifiers, and Profiles 頁面選擇 Keys 選項,然後新建一個Key。勾選WeatherKit 選項。
系統會提示你下載一個金鑰檔案,這是你呼叫WeatherKit服務的簽名用的私鑰,只能下載一次,一定要妥善保管到安全的位置,不能洩露。
然後記錄一下 生成的Key的 ID。稍後會用到。
二、開始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 欄位。
發起請求:
三、最後
個人 感覺WeatherKit REST API還是很不錯的,不需要額外付費就能有50萬次每月的訪問量,對於小體量的個人開發者完全夠用了。
還有一點值得注意的是,按照蘋果的要求,如果你使用了WeatherKit,你應該在APP中標明資料來源:
參考:
- 官方介面文件:Weather Kit REST API
-
WWDC 2022 WeatherKit 視訊: Meet WeatherKit - WWDC22 - Videos - Apple Developer
-
JWT 官方標準: JWT specification
- JWT 驗證工具:JWT.io