iOS內購 - 服務端票據驗證及漏單引發的思考

chenyxh2005發表於2020-12-21

因業務需要實現了APP內購處理,但在過程中出現了部分不可控的因素,導致部分使用者反映有充值不成並漏單的情況。

仔細考慮了幾個付費安全上的問題,凡是涉及到付費的問題都很敏感,任何一方出現損失都是不能接受的,所以在這裡整理一些支付安全的要點分享一下。

支付方式

IAP是指In-App Purchase, 是一種付費方式,而並不是蘋果專有的付費方式,在其它平臺上也會有不同的實現,這裡針對Apple IAP。

說到IAP安全問題,在蘋果的IAP流程中有一個比較明顯的邏輯漏洞,這個邏輯漏洞是建立在我們處理不當的情況下發生的,會導致己方提供的服務和使用者之間出現問題。先看看IAP支付時序圖:

支付流程

1.客戶端向Appstore請求購買產品(假設產品資訊已經取得),Appstore驗證產品成功後,從使用者的Apple賬戶餘額中扣費。

2.Appstore向客戶端返回一段receipt-data,裡面記錄了本次交易的證書和簽名資訊。

3.客戶端向我們可以信任的伺服器提供receipt-data

4.伺服器對receipt-data進行一次base64編碼

5.把編碼後的receipt-data發往itunes.appstore進行驗證

6.itunes.appstore返回驗證結果給伺服器

7.伺服器對商品購買狀態以及商品型別,向客戶端發放相應的道具與推送資料更新通知

8.客戶端收到伺服器的處理狀態,進行相應的結單處理

這八個步驟實際上是一個很安全的流程了。那問題出在哪裡呢?我們談談兩種蘋果IAP的驗證模型。

驗證方式

1.IAP built-in Model,本地驗證

有些APP甚至是網遊,都直接跳過了3~7步驟,在第2步拿到receipt-data之後,直接由客戶端向itunes.appstore傳送驗證請求,並且拿到結果,根據結果修改資料。

我們在設計APP的時候都遵循一個真理,“凡是在客戶端的資料都是不安全的”,深以為然。如果沒有獨立伺服器輔助驗證,這樣也就避免不了資料被修改的事實了,是的,你會少賺錢。

不過如果APP也不通過獨立伺服器驗證,而是在客戶端驗證之後再告知伺服器狀態讓其發放遊戲道具,那就太可怕了點。這是IAP built-in Model

那是不是就完全不能讓這個過程變得安全了呢?也不是,但這個安全保障只是讓修改變得困難而已。蘋果官方提供了 Validating Receipts Locally 在客戶端對receipt-data進行安全驗證,主要是對證書以及簽名的合法性驗證。如果不想自己寫程式碼驗證,也可以藉助第三方機構提供的receipt-data驗證API,比較著名的有  urbanairship和  beeblex 。

但如果能偽造一個完全合法的receipt-data,是不是一樣可以達到欺騙目的。是的,為了繞過Validating Locally,於是黑客開始用自己偽造的receipt-data進行移花接木,所以出現了可以偽造”合法訂單”的 in-appstore 。因此這種本地加強驗證的方法也不能完全避免IAP攻擊。

2.IAP Server Model,伺服器驗證

而如果我們把驗證邏輯移到伺服器上,這個過程就變得容易多了。因為不再需要擔心receipt-data被偽造的問題。不過就算把步驟4~7在伺服器上做了,同樣也會產生一些幼稚的邏輯漏洞

 對驗證receipt-data的reponse content不進行驗證和記錄,只根據Product直接發放商品。這樣只要客戶端不斷提交receipt-data,按照正常邏輯你就需要不斷驗證並且重複發放商品。較為安全的做法是:

在每一次收到receipt-data之後,都把提交的使用者賬號以及receipt-data中的單號建立對映並記錄下來,在每次驗證receipt-data時,先判斷其是否已經存在。

只要做了這樣的驗證,整個支付流程都變得明朗起來。

確保receipt-data的成功提交與異常處理

建立在IAP Server Model的基礎上,並且我們知道手機網路是不穩定的,在付款成功後不能確保把receipt-data一定提交到伺服器。如果出現了這樣的情況,那就意味著使用者被appstore扣費了,卻沒收到伺服器發放的道具。(這樣就引發了漏單)

解決這個問題的方法是在客戶端提交receipt-data給我們的伺服器,讓我們的伺服器向蘋果伺服器傳送驗證請求,驗證這個receipt-data賬單的有效性. 在沒有收到回覆之前,客戶端必須要把receipt-data儲存好,並且定期或在合理的UI介面觸發向服務端發起請求,直至收到服務端的回覆後刪除客戶端的receipt賬單記錄。

如果是客戶端沒成功提交receipt-data,那怎麼辦?就是使用者被扣費了,也收到appstore的消費收據了,卻依然沒收到道具,於是投訴到客服處。

這種情況在以往的經驗中也會出現,常見的使用者和運營商發生的糾紛。客服向使用者索要賬號和appstore的收據單號,通過查詢itunes-connect看是否確有這筆訂單。

如果訂單存在,則要聯絡研發方去查詢伺服器,看訂單號與使用者名稱是否對應,並且是否已經被使用了,做這一點檢查的目的是 為了防止惡意使用者利用已經使用過了的訂單號進行欺騙(已驗證的賬單是可以再次請求驗證的,曾經為了測試,將賬單手動發給伺服器處理併成功),謊稱自己沒收到商品。

當然了,如果查不到這個訂單號,就意味著這個訂單確實還沒使用過,手動給使用者補發商品即可。

有朋友問怎麼通過itunes-connect檢視具體訂單,itunes-connect中無法直接看到訂單資訊,可以用以下方法來查詢

1.可以通過賬單向蘋果傳送賬單驗證,有效可以手動補發

2.用自己的伺服器的記錄賬單列表對比 

3.利用第三方的TalkingData等交易函式,會自動記錄賬單資料

建議

為保證稽核的通過,需要在客戶端或server進行雙重驗證,即,先以線上交易驗證地址進行驗證,如果蘋果正式驗證伺服器的返回驗證碼code為21007,則再一次連線沙盒測試伺服器進行驗證即可。

在應用提審時,蘋果IAP提審驗證時是在沙盒環境的進行的,即:蘋果在稽核App時,只會在sandbox環境購買,其產生的購買憑證,也只能連線蘋果的測試驗證伺服器,如果沒有做雙驗證,需要特別注意此問題,否則會被拒。

其他

在sandbox中驗證receipt:https://sandbox.itunes.apple.com/verifyReceipt

在生產環境中驗證receipt:https://buy.itunes.apple.com/verifyReceipt

那麼如何自動的識別收據是否是sandbox receipt呢?
識別沙盒環境下收據的方法有兩種:

  1. 根據收據欄位 environment = sandbox。
  2. 根據收據驗證介面返回的狀態碼。
    如果status=21007,則表示當前的收據為沙盒環境下收據, 進行驗證。

蘋果反饋的狀態碼

  • 21000 App Store無法讀取你提供的JSON資料
  • 21002 收據資料不符合格式
  • 21003 收據無法被驗證
  • 21004 你提供的共享金鑰和賬戶的共享金鑰不一致
  • 21005 收據伺服器當前不可用
  • 21006 收據是有效的,但訂閱服務已經過期。當收到這個資訊時,解碼後的收據資訊也包含在返回內容中
  • 21007 收據資訊是測試用(sandbox),但卻被髮送到產品環境中驗證
  • 21008 收據資訊是產品環境中使用,但卻被髮送到測試環境中驗證

相關文章