本文的大部分內(nèi)容是對蘋果關(guān)于APNs官方文檔的翻譯以及整理。
一、設(shè)備token和消息的生命周期
關(guān)于設(shè)備token以及推送消息的生命周期需要注意下面幾點(diǎn):
- Token會在iOS系統(tǒng)更新或者設(shè)備數(shù)據(jù)、設(shè)置被擦除的時候改變。
- 當(dāng)設(shè)備離線的時候,APNS會將消息數(shù)據(jù)存儲一段時間,等設(shè)備上線后重發(fā)。如果設(shè)備在離線期間,向APNS發(fā)送了多條推送消息,APNS將會丟棄掉前面的一些消息,只保留后面的消息,要是設(shè)備長時間離線,則會將所有的消息丟棄掉。
- 可以通過設(shè)置http/2頭中的
apns-collapse-id鍵值對來合并消息,比如:apns-collapse-id : 2,那么value為2的消息將被APNS合并成一條消息推送給設(shè)備。
二、Provider(后臺)與APNs的交互
Provider(即,APP的后臺)與APNS有兩種安全的交互方式,都必須采用TLS以保證可靠性,更詳細(xì)的內(nèi)容繼續(xù)往下看。
TLS的簡單理解是,為了保證數(shù)據(jù)傳輸安全,在HTTP層與TCP層之間插入的一個安全校驗(yàn)層,它所做的事情簡單來說就是:“通過CA申請的證書驗(yàn)證client與server是可靠的之后,通過相應(yīng)的公私鑰加密一個協(xié)商的公鑰,之后的真實(shí)數(shù)據(jù)傳輸就使用這個公鑰進(jìn)行加密,以保證數(shù)據(jù)的安全可靠性”,關(guān)于TLS的更詳細(xì)的介紹,可以參考這篇文章
基于Token的方式(Token-Based Provider-to-APNs Trust)
1、流程概述
這種方式適合在基于HTTP/2協(xié)議的Provider使用,它與APNs之間的連接通過JWT(JSON web tokens)來驗(yàn)證。在這種方式下不需要使用證書+私鑰的方式來建立可靠連接。Provider只需要提供一對公私鑰(私鑰Provider自己保存,公鑰蘋果保存),并使用其中的私鑰生成并加密JWT Token,每次向APNs請求推送的時候帶上這個Token即可。
具體步驟如下:
- Provider通過
TLS向APNs發(fā)起請求。 - APNs返回一個證書給Provider。
- Provier驗(yàn)證這個證書。通過后,發(fā)送push數(shù)據(jù)并帶上JWT token。
- APNs驗(yàn)證token,并返回請求的結(jié)果。

建立TLS連接必須要有一個
GeoTrust Global CA root certificate,在macOS中,這個證書已經(jīng)安裝在keychain中,如果是其他操作系統(tǒng)則可以在GeoTrust Root Certificates website下載。
2、Provider Authentication Tokens
關(guān)于JWT(JSON Web Token)的詳細(xì)資料可以通過這里了解。同時也可以從這里找到一些現(xiàn)成可用的庫。
下面對JWT進(jìn)行詳細(xì)的介紹,一個JWT實(shí)際上是一個JSON對象,它的頭部必須包含:
- 用以加密token的加密算法(alg) ,比如:ES256。
- 10個字符長度的標(biāo)識符(kid),(登入蘋果開發(fā)者賬號后,進(jìn)入到Certificates, Identifiers & Profiles,然后點(diǎn)擊APNs Auth Key,最后在右側(cè)找到Apple Push Notification Authentication Key (Sandbox & Production)選項(xiàng),點(diǎn)擊創(chuàng)建后可以創(chuàng)建一個p8文件。)
同時他的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
}
創(chuàng)建完這個token后,必須使用自己的私鑰對其進(jìn)行加密,然后再采用基于P-256曲線和SHA-256哈希算法的橢圓曲線數(shù)字簽名算法(ECDSA)進(jìn)行簽名,并將alg鍵的值設(shè)置為ES256。(注意:APNs只支持ES256簽名的JWT,否則會返回InvalidProviderToken(403)錯誤)
為了保證安全,APNs要求定期更新token,時間間隔為1小時,如果APNs發(fā)現(xiàn)當(dāng)前的時間戳與iat值中的時間戳相比,大于一個小時,那么APNs會拒絕推送消息,并返回ExpiredProviderToken (403)錯誤。
基于證書的方式(Certificate-based connection trust)
流程概述
這種方式是指Provider可以采用一個唯一的證書以及一個加密的私鑰來與APNs交互,其中證書是由蘋果產(chǎn)生的(通過蘋果賬號登錄到developer account配置創(chuàng)建)。整個交互過程如下:
- Provider通過
TLS向APNs請求連接。 - APNs向Provider返回一個APNs證書。
- Provider驗(yàn)證這個APNs證書,并將從蘋果官網(wǎng)獲取的證書返回給APNs。
- APNs驗(yàn)證通過后,這個鏈接就算是建立了。

APNs Provider Certificates
創(chuàng)建步驟可以參考Configure push notifications中的Generate a universal APNs client SSL certificate章節(jié)。
三、APNs連接
連接的管理
蘋果的兩個APNs server分別為:
-
Development server:
api.development.push.apple.com:443 -
Production server:
api.push.apple.com:443
要與APNs交互要求server必須支持1.2及上版本的TLS協(xié)議。通過上面的介紹我們已經(jīng)知道,server跟APNs交互有兩種方式:基于JWT的方式以及基于證書的方式。為了保證高質(zhì)量的使用APNs應(yīng)該注意如下幾點(diǎn):
- 對于基于JWT的方式,應(yīng)該定時更新token,token的有效期為1小時。
- 對于基于JWT的方式,能每次請求都創(chuàng)建新的token,盡量在一小時內(nèi)使用同一個token。
- 不能頻繁的建立、關(guān)閉連接,否則APNs會把這當(dāng)做是黑客攻擊,拒絕訪問。應(yīng)該盡量將連接?;?,直到你認(rèn)為這個連接接下來會長時間不使用為止。
- 當(dāng)需要發(fā)送大量的推送數(shù)據(jù)的時候,可以同時創(chuàng)建多個連接,以改善性能。
- 當(dāng)?shù)蹁N證書或者token時,應(yīng)該關(guān)閉所有相關(guān)的連接。
HTTP/2的請求與響應(yīng)
詳情可參見蘋果官方文檔HTTP/2 Request to APNs。這里介紹了接口的請求參數(shù)、返回結(jié)果,錯誤碼以及示例代碼。下面僅截取了其中的例子,以加深對APNs的使用的理解:
-
基于證書的方式的request:
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 //可以不填,如果不填A(yù)PNs會自己創(chuàng)建一個UUID并在response中返回 apns-expiration = 0 apns-priority = 10 DATA + END_STREAM { "aps" : { "alert" : "Hello" } } -
基于token方式的request:
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" } } -
失敗后的response:
HEADERS - END_STREAM + END_HEADERS :status = 400 content-type = application/json apns-id: <a_UUID> DATA + END_STREAM { "reason" : "BadDeviceToken" } -
成功后的response:
HEADERS - END_STREAM + END_HEADERS apns-id = eabeae54-14a8-11e5-b60b-1697f925ec7b :status = 200
四、設(shè)備token的生成與分發(fā)
在app啟動的時候,必須向iOS系統(tǒng)注冊遠(yuǎn)程推送,成功后,蘋果將會返回一個設(shè)備token給app,此時app就可以將這個token上報給自己的后臺。
如果有必要產(chǎn)生一個新的token,APNs會使用設(shè)備證書生成一個token(其中包含了一個設(shè)備ID),并使用token key加密后返回給設(shè)備。同時設(shè)備會將這個token以NSData對象的形式返回給app,app獲取到該token之后應(yīng)該將其發(fā)送到自己后臺,后臺之后就可以通過這個token來發(fā)送推送數(shù)據(jù)。過程如下圖:

最后
通過蘋果的官方文檔我們可以知道provider與APNs的交互過程中,需要注意一下幾點(diǎn):
- 推薦使用
HTTP/2協(xié)議。 - 必須加入
TLS層。 - 基于JWT的方式,token的最大有效期為1小時,并且不能頻繁更換token。
- 不能頻繁創(chuàng)建、關(guān)閉連接,應(yīng)該盡量少開連接,如果過于頻繁,APNs將把其當(dāng)做是黑客攻擊,但是如果數(shù)據(jù)量大,可以同時多個連接向APNs發(fā)送消息。
- 吊銷token或者證書的時候,應(yīng)該及時關(guān)閉老的連接。