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


参考: