iOS开发多App推送还申请多推送证书?我只能说你out了!
新的APNs协议基于HTTP/2,一种是使用Universal Push Notification Client SSL 证书,一种是使用Token,本文主要讲基于token的APNs协议。
基于HTTP/2与Token的 APNs 协议
APNs Provider(即,APP的后台) API 允许您向您的 iOS,macOS 设备上的应用程序和 Apple Watch 发送远程通知。API 基于 HTTP/2 网络协议。每个交互通过一个 POST 请求,包含 JSON 的有效Payload负载,通过服务器使用Auth Key生成服务端token连接APNs服务器,并且通过设备token发送负载。APNs然后转发给特定设备的指定应用程序。
Request 和 Response 使用JSON通信
APNs支持状态码和返回 error 信息
APNs推送成功时 Response 将返回状态码200
APNs推送失败时,Response 将返回 JSON 格式的 Error 信息。
最大推送长度提升到4096字节(4Kb)
可以通过 "HTTP/2 PING " 心跳包功能检测当前 APNs 连接是否可用,并能维持当前长连接。
支持为不同的APP定义 "topic"(其实就是App Bundle ID)
多个推送App,只需要一个Apple Push APNs Auth Key
Apple Push Notification Authentication Key
2016年9月,苹果悄悄上线了token验证的推送方式,通过获得一个认证密钥(APNs Auth Key)去生成服务器端token,并且token非常容易生成,可以使用这些token令牌代替推送证书。一个认证密钥可用于多个应用程序并且永远不过期。每一个需要推送的App都需要配置推送证书的时代过去了。but,大部分第三方推送服务商,目前都没有升级到APNs Auth Key Token模式。
Auth key 生成
开发者网站证书页面:https://developer.apple.com/account/ios/certificate/key选择Apple Push Notification Authentication Key (Sandbox & Production)并且点击页面底部的Continue
网站会生成包含APNs Auth Key的.p8密钥文件,下载下来APNsAuthKey_xxxxxxx.p8后续连接APNS使用。
协议说明
这种方式适合在基于HTTP/2协议的Provider使用,它与APNs之间的连接通过JWT(JSON web tokens)来验证。在这种方式下不需要使用证书+私钥的方式来建立可靠连接。Provider只需要提供一对公私钥(私钥给APNs保存,公钥Provider自己保存),并使用其中的私钥生成并加密JWT Token,每次向APNs请求推送的时候带上这个Token即可。token必须定期更新,每一个APNs provider验证token有效期为一个小时。具体步骤如下:
Provider通过
TLS
向APNs发起请求。APNs返回一个证书给Provider。
Provier验证这个证书。通过后,发送push数据并带上JWT token。
APNs验证token,并返回请求的结果。
重要:建立TLS连接必须要有一个GeoTrust Global CA root certificate
,在macOS中,这个证书已经安装在keychain中,如果是其他操作系统则可以在GeoTrust Root Certificates website下载。
HTTP/2请求APNs
HTTP/2 请求字段
字段名 | 字段值 |
---|---|
:method | POST |
:path | /3/device/ |
对于
请求头
APNs 请求头 http headers
Header 头 | 描述 |
---|---|
authorization | 提供的发送到指定(app)主题通知的APNs验证token,token是以Base64 URL编码的JWT格式。指定为bearer 当使用证书连接的时候,这个请求头将会被忽略 |
apns-id | 一个规范的的 UUID 用来标识通知。如果发送通知时发生错误,APNs 使用此值来标识通知,通知到您的服务器。规范的格式是 32 个小写的十六进制数字,由连字元分隔为5,8-4-4-4-12。UUID一个例子如下:123e4567-e89b-12d3-a456-42665544000 如果您省略这个头,一个新的UUID由APNs创建并且在response中返回 |
apns-expiration | 通知过期时间,秒级的UTC时间戳,这个header标识通知 从何时起不再有效,可以丢弃。如果这个值非零,APNs保存通知并且尽量至少送达一次。如果无法第一时间送达,根据需要重复尝试。如果这个值为0,APNs认为通知立即过期,不会存储与重新推送。 |
apns-priority | 通知的优先级,指定以下值:
|
apns-topic | 远程通知的主题,通常是你App的bundle id,在开发者账号中创建的证书必须包含此bundle id如果证书包含多个主题,这个头必须指定一个值如果省略此头并且APNs证书不包含指定的主题,APNs服务器使用证书的Subject作为默认主题。如果使用token代替证书,必须指定此头,提供的主题应该被在开发者账号中的team提供。即bundle id的app应该与auth key同属于一个开发者组。 |
apns-collapse-id | 具有相同的折叠标识符的多个通知推送给用户合并显示为单个通知。比如: apns-collapse-id : 2 ,那么value为2的消息将被APNS合并成一条消息推送给设备。此关键字的值不能超过 64 个字节。更多的信息,请参阅Quality of Service, Store-and-Forward, and Coalesced Notifications。 |
Provider Authentication Tokens
关于JWT(JSON Web Token)的详细资料可以通过这里了解。同时也可以从这里找到一些现成可用的库。下面对JWT进行详细的介绍,一个JWT实际上是一个JSON对象,它的头部必须包含:
用以加密token的加密算法(alg) ,比如:ES256。
10个字元长度的标识符(kid),(苹果开发者网站创建的APNs Auth Key详情中的key id)
同时他的claims payload部分必须包含:
issuer(iss) registered claim key,其值就是10个字元长的Team ID。
issued at (iat) registered claim key,其值是一个秒级的UTC时间戳。
比如:
{
创建完这个token后,必须使用自己的私钥对其进行加密,然后再采用基于P-256曲线和SHA-256哈希算法的椭圆曲线数字签名算法(ECDSA)进行签名,并将alg
键的值设置为ES256
。(注意:APNs只支持ES256签名的JWT,否则会返回InvalidProviderToken(403)
错误)为了保证安全,APNs要求定期更新token,时间间隔为1小时,如果APNs发现当前的时间戳与iat
值中的时间戳相比,大于一个小时,那么APNs会拒绝推送消息,并返回ExpiredProviderToken (403)
错误。
请求体
body内容是将要推送的消息负载的JSON对象。body数据不必须压缩,其最大大小为4 KB(4096字节)。对于(VoIP)通知,body数据最大大小为5 KB(5120字节)。
{ "aps" : { "alert" : "Hello HTTP/2" } }
有关负载中的具体的键和值,请参阅Payload Key Reference。
响应头
APNs 响应头 response headers
Header 名 | Header值 |
---|---|
apns-id | apns-id 从request得到, 如果request中没有此值, APNs服务器创建一个新的UUID并且在header中返回 |
:status | HTTP状态码,HTTP status code.可能status codes, 参加下文 |
响应状态码
Status codes for an APNs response
Status code | Description |
---|---|
200 | 成功 |
400 | 无效请求 |
403 | 证书错误或者验证token错误 |
405 | :method 设置错误. 只支持 POST 请求 |
410 | device token不在有效与topic |
413 | 负载payload太大 |
429 | 服务端对于同一个device token发送了太多了请求 |
500 | 内部服务器错误 |
503 | 服务器关闭,不可用 |
使用
Sample request for a provider authentication token
HEADERS
- END_STREAM
+ END_HEADERS
:method = POST
:scheme = https
:path = /3/device/00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0
host = api.development.push.apple.com
authorization = bearer eyAia2lkIjogIjhZTDNHM1JSWDciIH0.eyAiaXNzIjogIkM4Nk5WOUpYM0QiLCAiaWF0I
jogIjE0NTkxNDM1ODA2NTAiIH0.MEYCIQDzqyahmH1rz1s-LFNkylXEa2lZ_aOCX4daxxTZkVEGzwIhALvkClnx5m5eAT6
Lxw7LZtEQcH6JENhJTMArwLf3sXwi
apns-id = eabeae54-14a8-11e5-b60b-1697f925ec7b
apns-expiration = 0
apns-priority = 10
apns-topic =
DATA
+ END_STREAM
{ "aps" : { "alert" : "Hello" } }
APNs Auth Key 与 shell
#!/bin/bash
# Get curl with HTTP/2 and openssl with ECDSA: 'brew install curl openssl'
curl=/usr/local/opt/curl/bin/curl
openssl=/usr/local/opt/openssl/bin/openssl
# --------------------------------------------------------------------------
# HostDevelopment = "https://api.development.push.apple.com"
# HostProduction = "https://api.push.apple.com"
deviceToken=b27371497b85611baf9052b4ccfb9641ab7fea1d01c91732149c99cc3ed9342f
authKey="./APNSAuthKey_ABC1234DEF.p8"
authKeyId=ABC1234DEF
teamId=TEAM123456
bundleId=com.example.myapp
endpoint=https://api.development.push.apple.com
read -r -d '' payload <<-'EOF'
{
"aps": {
"badge": 2,
"category": "mycategory",
"alert": {
"title": "my title",
"subtitle": "my subtitle",
"body": "my body text message"
}
},
"custom": {
"mykey": "myvalue"
}
}
EOF
# --------------------------------------------------------------------------
base64() {
$openssl base64 -e -A | tr -- '+/' '-_' | tr -d =
}
sign() {
printf "$1"| $openssl dgst -binary -sha256 -sign "$authKey" | base64
}
time=$(date +%s)
header=$(printf '{ "alg": "ES256", "kid": "%s" }' "$authKeyId" | base64)
claims=$(printf '{ "iss": "%s", "iat": %d }' "$teamId" "$time" | base64)
jwt="$header.$claims.$(sign $header.$claims)"
$curl --verbose \
--header "content-type: application/json" \
--header "authorization: bearer $jwt" \
--header "apns-topic: $bundleId" \
--data "$payload" \
$endpoint/3/device/$deviceToken
后续更新其他语言推送方法。
参考资料
CommunicatingwithAPNs
http://thrysoee.dk/apns/