Note: For licensing in online apps and games, we recommend using the Play Integrity API.
Learn more .
為應用程式新增伺服器端授權驗證
透過集合功能整理內容
你可以依據偏好儲存及分類內容。
驗證使用者是否已透過 Google Play 商店購買或下載應用程式的合法副本時,建議您在控制的伺服器上執行授權驗證檢查。
本指南將逐步說明完成伺服器端授權驗證的流程,並介紹執行這項檢查作業的相關最佳做法。
流程總覽
圖 1 顯示了在應用程式、Google Play 和私人伺服器之間轉移資訊的方式:
圖 1. 應用程式與 Google Play 之間,以及應用程式與私人伺服器之間的資料流
應用程式向 Google Play 傳送請求,詢問特定使用者是否已購買或下載應用程式的合法副本。
Google Play 會透過向應用程式傳送「回應資料物件」 (物件類型為 ResponseData
) 來做出回應。這個物件是一份簽署資訊,指示使用者是否已購買或下載應用程式的合法副本。
應用程式會向您控制的私人伺服器傳送請求,以便驗證回應資料的內容。
伺服器會將狀態傳送給應用程式,以指示使用者是否已購買或下載應用程式的合法副本。如果伺服器提供「成功」訊息,請驗證回應 ,然後將需要授權的資源存取權授予使用者。
由於回應資料由 Google Play 簽署,而後在伺服器中進行檢查,因此無法在執行應用程式的裝置上修改物件。如果應用程式仰賴伺服器,並僅向合法使用者開放資源,則應用程式將受到更大程度的保護,以避免受未經授權使用者的侵害。
以下各節將提供其他的注意事項,提醒您何時應執行伺服器端授權驗證。
防範重播攻擊
使用者收到 Google Play 的使用者授權狀態回應後,就能複製回應資料並多次使用,或提供給其他使用者,他們就可以偽造自己的請求,並傳送給應用程式的私人伺服器。這種操作稱為「重播攻擊」 。
為降低使用者成功執行重播攻擊的可能性,請在向應用程式伺服器傳送請求前採取以下措施:
檢查回應資料中包含的時間戳記,確保 Google Play 是在最近產生回應。
注意 :您可以根據使用者在停用授權後與授權綁定資源互動的時間長度,增加回應資料時間戳記和目前時間之間的允許差值。
對伺服器請求執行頻率限制 (例如指數輪詢),以減少應用程式嘗試向應用程式伺服器傳送相同回應資料的次數。
注意 :為讓使用者在各種裝置上與應用程式互動時保持良好的使用者體驗,請謹慎決定是否要根據裝置數量增加頻率限制。
在私人伺服器上驗證 Google Play 回應資料的內容之前,請先向私人伺服器發出初始驗證請求。在第一個請求中,將使用者憑證傳送至您的伺服器,然後讓伺服器以「Nonce」 或僅使用一次的數字做出回應。接著您可以在下一個要對私人伺服器傳送的請求中加入這個 Nonce,請求取得授權驗證資料。如要進一步瞭解如何為 Nonce 選擇最佳值,請參閱「產生合適的 Nonce 值 」一節。
注意 :在 Nonce 請求和授權驗證請求中都要加入使用者 ID 欄位,應用程式伺服器就能比較兩個請求中的欄位值,並確認兩者相符。
產生合適的 Nonce 值
請使用下列其中一種技巧,建立難以猜到的 Nonce 值:
根據使用者 ID 產生雜湊值。
為每位使用者產生隨機值。將這個隨機值儲存在應用程式伺服器上,做為特定使用者屬性的一部分。
驗證來自伺服器的回應資料
查看應用程式伺服器傳送給應用程式的回應資料時,請確認授權驗證庫的回應並非偽造。請將應用程式伺服器回應資料中包含的簽名與應用程式在上個步驟中從 Google Play 收到的金鑰進行比較,以驗證簽名。
另外也請注意,授權驗證庫 (LVL) 的專屬區塊是唯一已簽署的部分。因此,在應用程式伺服器的回應資料中,應用程式只應該信任這一個部分。
這個頁面中的內容和程式碼範例均受《內容授權 》中的授權所規範。Java 與 OpenJDK 是 Oracle 和/或其關係企業的商標或註冊商標。
上次更新時間:2024-01-10 (世界標準時間)。
[{
"type": "thumb-down",
"id": "missingTheInformationINeed",
"label":"缺少我需要的資訊"
},{
"type": "thumb-down",
"id": "tooComplicatedTooManySteps",
"label":"過於複雜/步驟過多"
},{
"type": "thumb-down",
"id": "outOfDate",
"label":"過時"
},{
"type": "thumb-down",
"id": "translationIssue",
"label":"翻譯問題"
},{
"type": "thumb-down",
"id": "samplesCodeIssue",
"label":"示例/程式碼問題"
},{
"type": "thumb-down",
"id": "otherDown",
"label":"其他"
}]
[{
"type": "thumb-up",
"id": "easyToUnderstand",
"label":"容易理解"
},{
"type": "thumb-up",
"id": "solvedMyProblem",
"label":"確實解決了我的問題"
},{
"type": "thumb-up",
"id": "otherUp",
"label":"其他"
}]
{"lastModified": "\u4e0a\u6b21\u66f4\u65b0\u6642\u9593\uff1a2024-01-10 (\u4e16\u754c\u6a19\u6e96\u6642\u9593)\u3002"}
[[["容易理解","easyToUnderstand","thumb-up"],["確實解決了我的問題","solvedMyProblem","thumb-up"],["其他","otherUp","thumb-up"]],[["缺少我需要的資訊","missingTheInformationINeed","thumb-down"],["過於複雜/步驟過多","tooComplicatedTooManySteps","thumb-down"],["過時","outOfDate","thumb-down"],["翻譯問題","translationIssue","thumb-down"],["示例/程式碼問題","samplesCodeIssue","thumb-down"],["其他","otherDown","thumb-down"]],["上次更新時間:2024-01-10 (世界標準時間)。"]]