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