iOS 开发之新版 APNs 搭建必备知识

zhan71 3年前
   <h2>一、设备token和消息的生命周期</h2>    <p>关于设备token以及推送消息的生命周期需要注意下面几点:</p>    <ul>     <li>Token会在iOS系统更新或者设备数据、设置被擦除的时候改变。</li>     <li>当设备离线的时候,APNS会将消息数据存储一段时间,等设备上线后重发。如果设备在离线期间,向APNS发送了多条推送消息,APNS将会丢弃掉前面的一些消息,只保留后面的消息,要是设备长时间离线,则会将所有的消息丢弃掉。</li>     <li>可以通过设置http/2头中的 apns-collapse-id 键值对来合并消息,比如: apns-collapse-id : 2 ,那么value为2的消息将被APNS合并成一条消息推送给设备。</li>    </ul>    <h2>二、Provider(后台)与APNs的交互</h2>    <p>Provider(即,APP的后台)与APNS有两种安全的交互方式,都必须采用TLS以保证可靠性,更详细的内容继续往下看。</p>    <p>TLS 的简单理解是,为了保证数据传输安全,在HTTP层与TCP层之间插入的一个安全校验层,它所做的事情简单来说就是: “通过CA申请的证书验证client与server是可靠的之后,通过相应的公私钥加密一个 协商的公钥 ,之后的真实数据传输就使用这个公钥进行加密,以保证数据的安全可靠性” ,关于TLS的更详细的介绍</p>    <h3>基于Token的方式(Token-Based Provider-to-APNs Trust)</h3>    <p>1、流程概述</p>    <p>这种方式适合在基于HTTP/2协议的Provider使用,它与APNs之间的连接通过 JWT(JSON web tokens) 来验证。在这种方式下不需要使用证书+私钥的方式来建立可靠连接。Provider只需要提供一对公私钥(私钥给APNs保存,公钥Provider自己保存),并使用其中的私钥生成并加密JWT Token,每次向APNs请求推送的时候带上这个Token即可。</p>    <p>具体步骤如下:</p>    <ol>     <li>Provider通过 TLS 向APNs发起请求。</li>     <li>APNs返回一个证书给Provider。</li>     <li>Provier验证这个证书。通过后,发送push数据并带上JWT token。</li>     <li>APNs验证token,并返回请求的结果。</li>    </ol>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c618bd00930a585bc2353e4655790640.png"></p>    <p style="text-align:center">Establishing and using token-based connection trust between a provider and APNs</p>    <p>建立TLS连接必须要有一个 GeoTrust Global CA root certificate ,在macOS中,这个证书已经安装在keychain中,如果是其他操作系统则可以在 GeoTrust Root Certificates website 下载。</p>    <p>2、Provider Authentication Tokens</p>    <p>关于JWT(JSON Web Token)的详细资料可以通过 这里 了解。同时也可以从 这里 找到一些现成可用的库。</p>    <p>下面对JWT进行详细的介绍,一个JWT实际上是一个JSON对象,它的头部必须包含:</p>    <ul>     <li>用以加密token的加密算法( <strong>alg</strong> ) ,比如:ES256。</li>     <li>10个字符长度的标识符( <strong>kid</strong> ),(登入苹果开发者账号后,进入到 <strong>Certificates, Identifiers & Profiles</strong> ,然后点击 <strong>APNs Auth Key</strong> ,最后在右侧找到 Apple Push Notification Authentication Key (Sandbox & Production) 选项,点击创建后可以创建一个p8文件。)</li>    </ul>    <p>同时他的claims payload部分必须包含:</p>    <ul>     <li>issuer( <strong>iss</strong> ) registered claim key,其值就是10个字符长的Team ID。</li>     <li>issued at ( <strong>iat</strong> ) registered claim key,其值是一个秒级的UTC时间戳。</li>    </ul>    <p>比如:</p>    <pre>  <code class="language-objectivec">{      "alg": "ES256",      "kid": "ABC123DEFG"  }  {      "iss": "DEF123GHIJ",      "iat": 1437179036   }</code></pre>    <p>创建完这个token后,必须使用自己的私钥对其进行加密,然后再采用基于P-256曲线和SHA-256哈希算法的椭圆曲线数字签名算法(ECDSA)进行签名,并将 alg 键的值设置为 ES256 。(注意:APNs只支持 <strong>ES256</strong> 签名的JWT,否则会返回 InvalidProviderToken(403) 错误)</p>    <p>为了保证安全,APNs要求定期更新token,时间间隔为1小时,如果APNs发现当前的时间戳与 iat 值中的时间戳相比,大于一个小时,那么APNs会拒绝推送消息,并返回 ExpiredProviderToken (403) 错误。</p>    <h3>基于证书的方式(Certificate-based connection trust)</h3>    <p>流程概述</p>    <p>这种方式是指Provider可以采用一个唯一的证书以及一个加密的私钥来与APNs交互,其中证书是由苹果产生的(通过苹果账号登录到 developer account 配置创建)。整个交互过程如下:</p>    <ol>     <li>Provider通过 TLS 向APNs请求连接。</li>     <li>APNs向Provider返回一个APNs证书。</li>     <li>Provider验证这个APNs证书,并将从苹果官网获取的证书返回给APNs。</li>     <li>APNs验证通过后,这个链接就算是建立了。</li>    </ol>    <p style="text-align:center"><img src="https://simg.open-open.com/show/337e679d837218ca15adb5aa195916b5.png"></p>    <p style="text-align:center">Establishing certificate-based connection trust between a provider and APNs</p>    <p>APNs Provider Certificates</p>    <p>创建步骤可以参考 Configure push notifications 中的Generate a universal APNs client SSL certificate章节。</p>    <h2>三、APNs连接</h2>    <h3>连接的管理</h3>    <p>苹果的两个APNs server分别为:</p>    <ul>     <li><strong>Development server:</strong> api.development.push.apple.com:443</li>     <li><strong>Production server:</strong> api.push.apple.com:443</li>    </ul>    <p>要与APNs交互要求server必须支持1.2及上版本的TLS协议。通过上面的介绍我们已经知道,server跟APNs交互有两种方式:基于JWT的方式以及基于证书的方式。为了保证高质量的使用APNs应该注意如下几点:</p>    <ul>     <li>对于基于JWT的方式,应该定时更新token,token的有效期为1小时。</li>     <li>对于基于JWT的方式,能每次请求都创建新的token,尽量在一小时内使用同一个token。</li>     <li>不能频繁的建立、关闭连接,否则APNs会把这当做是黑客攻击,拒绝访问。应该尽量将连接保活,直到你认为这个连接接下来会长时间不使用为止。</li>     <li>当需要发送大量的推送数据的时候,可以同时创建多个连接,以改善性能。</li>     <li>当吊销证书或者token时,应该关闭所有相关的连接。</li>    </ul>    <h3>HTTP/2的请求与响应</h3>    <p>详情可参见苹果官方文档 HTTP/2 Request to APNs 。这里介绍了接口的 <strong>请求参数</strong> 、 <strong>返回结果</strong> , <strong>错误码</strong> 以及 <strong>示例代码</strong> 。下面仅截取了其中的例子,以加深对APNs的使用的理解:</p>    <ul>     <li> <p>基于证书的方式的request:</p> <pre>  <code class="language-objectivec">HEADERS      - END_STREAM      + END_HEADERS      :method = POST      :scheme = https      :path = /3/device/00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0      host = api.development.push.apple.com      apns-id = eabeae54-14a8-11e5-b60b-1697f925ec7b //可以不填,如果不填APNs会自己创建一个UUID并在response中返回      apns-expiration = 0      apns-priority = 10    DATA      + END_STREAM        { "aps" : { "alert" : "Hello" } }</code></pre> </li>     <li> <p>基于token方式的request:</p> <pre>  <code class="language-objectivec">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" } }</code></pre> </li>     <li> <p>失败后的response:</p> <pre>  <code class="language-objectivec">HEADERS      - END_STREAM      + END_HEADERS      :status = 400      content-type = application/json        apns-id: <a_UUID>    DATA      + END_STREAM      { "reason" : "BadDeviceToken" }</code></pre> </li>     <li> <p>成功后的response:</p> <pre>  <code class="language-objectivec">HEADERS      - END_STREAM      + END_HEADERS        apns-id = eabeae54-14a8-11e5-b60b-1697f925ec7b        :status = 200</code></pre> </li>    </ul>    <h2>四、设备token的生成与分发</h2>    <p>在app启动的时候,必须向iOS系统注册远程推送,成功后,苹果将会返回一个设备token给app,此时app就可以将这个token上报给自己的后台。</p>    <p>如果有必要产生一个新的token,APNs会使用设备证书生成一个token(其中包含了一个设备ID),并使用token key加密后返回给设备。同时设备会将这个token以 NSData 对象的形式返回给app,app获取到该token之后应该将其发送到自己后台,后台之后就可以通过这个token来发送推送数据。过程如下图:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/ef81e3746aa5690241d33246c17a3ade.png"></p>    <p style="text-align:center">Managing the device token</p>    <h2>最后</h2>    <p>通过苹果的官方文档我们可以知道provider与APNs的交互过程中,需要注意一下几点:</p>    <ul>     <li>推荐使用 HTTP/2 协议。</li>     <li>必须加入 TLS 层。</li>     <li>基于JWT的方式,token的最大有效期为1小时,并且不能频繁更换token。</li>     <li>不能频繁创建、关闭连接,应该尽量少开连接,如果过于频繁,APNs将把其当做是黑客攻击,但是如果数据量大,可以同时多个连接向APNs发送消息。</li>     <li>吊销token或者证书的时候,应该及时关闭老的连接。</li>    </ul>    <h2>参考文献</h2>    <ol>     <li><a href="/misc/goto?guid=4959731351673729900" rel="nofollow,noindex">APNs Overview</a></li>     <li><a href="/misc/goto?guid=4959731351764095825" rel="nofollow,noindex">Communicating with APNs</a></li>    </ol>    <p> </p>    <p>来自:http://www.jianshu.com/p/d8dba6c2c07a</p>    <p> </p>