最新消息:iOS编程开发交流群(6906921) ,Mac.Cocoa开发交流群(7758675) 欢迎iOS/macOS开发编程爱好及学习者加入!

基于HTTP/2与Token的APNs新协议

iOS 天狐 49937浏览 5评论

新的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有效期为一个小时。

具体步骤如下:

  1. Provider通过TLS向APNs发起请求。
  2. APNs返回一个证书给Provider。
  3. Provier验证这个证书。通过后,发送push数据并带上JWT token。
  4. APNs验证token,并返回请求的结果。

重要:

建立TLS连接必须要有一个GeoTrust Global CA root certificate,在macOS中,这个证书已经安装在keychain中,如果是其他操作系统则可以在GeoTrust Root Certificates website下载。

HTTP/2请求APNs

HTTP/2 请求字段
字段名 字段值
:method POST
:path /3/device/<device-token>

对于 <device-token> 参数,指定目标设备的十六进制字节。

APNs需要使用 HPACK (HTTP/2 的报头压缩),防止重复的标题键和值。APNs为HPACK维护一个小的动态表。为了避免填满了APNs HPACK表,因而必须丢弃表数据,编码headers以下列方式 — — 尤其是当发送大量的流︰

:method 必须为POST

:path 值应编码为一个header中的一个文本字段,没有索引

authorization 请求header,如果存在,应编码为一个header中的一个文本字段,没有索引

apns-id、 apns-expiration,和 apns-collapse-id 不同的编码取决于它是否是第一次初始请求或随后的 POST 操作的一部分,如下︰

第一次发送请求头的时,使增量索引编码允许头名称加入动态表中

子请求发送头时,编码成header中的一个文本字段,没有索引

请求头

APNs 请求头 http headers
Header 头 描述
authorization 提供的发送到指定(app)主题通知的APNs验证token,token是以Base64 URL编码的JWT格式。指定为bearer <provider token>

当使用证书连接的时候,这个请求头将会被忽略

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 通知的优先级,指定以下值:

  • 10–立即推送消息. 这个优先级的通知必须再目标设备上触发alert, 声音, 或者badge . 仅仅包含content-available key使用此优先级是错误的。
  • 5—一次性发送通知,需要考虑设备的电量。具有此优先级的通知可能分组推送并且几种爆发推送  Notifications with this priority might be grouped and delivered他们会被节流并且某些情况下送达

如果省略这个头,APNs服务器会把优先级设置为10

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时间戳。

比如:

{
    "alg": "ES256",
    "kid": "ABC123DEFG"
}
{
    "iss": "DEF123GHIJ",
    "iat": 1437179036
}

创建完这个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 = <MyAppTopic>
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/

 

 

转载请注明:天狐博客 » 基于HTTP/2与Token的APNs新协议

微信 OR 支付宝 扫描二维码
为天狐 打赏
非常感谢你的支持,哥会继续努力!
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

网友最新评论 (5)

  1. 你好!可不可把你那个p8文件发给我, 运行测试一下这个脚本呢?目前我们这个证书还没有申请下来。
    lang9年前 (2017-06-02)回复
    • 这。。。。。
      天狐9年前 (2017-06-06)回复
  2. 哈哈,真有趣
    dd8年前 (2017-09-12)回复
  3. 他那个p8生成的token和P12的那个token有什么区别,p8的要针对做什么处理吗
    大佬7年前 (2018-09-14)回复
  4. 有没有java的demo
    大佬7年前 (2018-09-14)回复