注意:針對線上應用程式和遊戲的授權機制,建議使用 Play Integrity API。瞭解詳情

為應用程式新增用戶端授權驗證

Stay organized with collections Save and categorize content based on your preferences.

警告:當應用程式在用戶端執行授權驗證程序時,潛在攻擊者會較為容易修改或移除與此驗證程序相關的邏輯。

因此,我們強烈建議您改為執行伺服器端授權驗證

設定發布者帳戶和開發環境後 (請參閱設定授權),即可使用授權驗證庫 (LVL) 為應用程式新增授權驗證。

使用 LVL 新增授權驗證時,必須完成以下工作:

  1. 在應用程式的資訊清單中新增授權權限
  2. 實作一項政策 — 您可以選擇使用 LVL 提供的完整做法之一,也可以自行建立。
  3. 如果 Policy 會快取任何授權回應資料,請實作一個模糊處理工具
  4. 在應用程式的主活動中新增用於檢查授權的程式碼
  5. 實作 DeviceLimiter (為選擇性,對大多數應用程式來說不建議)。

下列章節詳細說明了這些工作。完成整合作業後,您應能夠成功編譯應用程式,並依照設定測試環境中所述的方式開始測試。

如需 LVL 中包含的完整原始檔總覽,請參閱LVL 類別和介面匯總

新增授權權限

若要使用 Google Play 應用程式傳送授權檢查至伺服器,您的應用程式必須要求適當的權限,com.android.vending.CHECK_LICENSE。如果應用程式未宣告授權權限,但嘗試啟動授權檢查,LVL 會擲回安全性例外狀況。

如要在應用程式中宣求授權權限,請將 <uses-permission> 元素宣告為 <manifest> 的子項,如下所示:

<uses-permission android:name="com.android.vending.CHECK_LICENSE" />

以下舉例説明 LVL 範例應用程式如何宣告權限:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...">
    <!-- Devices >= 3 have version of Google Play that supports licensing. -->
    <uses-sdk android:minSdkVersion="3" />
    <!-- Required permission to check licensing. -->
    <uses-permission android:name="com.android.vending.CHECK_LICENSE" />
    ...
</manifest>

注意:目前您無法在 LVL 程式庫專案的資訊清單中宣告 CHECK_LICENSE 權限,因為 SDK 工具不會將其合併至相依的應用程式資訊清單中。相反地,您必須在每個相依應用程式的資訊清單中宣告權限。

實作一項政策

Google Play 授權服務本身並不會判斷是否應向具有指定授權的指定使用者授予應用程式的存取權,而是由您在應用程式中實作的 Policy 負責這件事情。

政策是指一個由 LVL 宣告的介面,用來根據授權檢查的結果保留應用程式的邏輯,以允許或禁止使用者存取。若要使用 LVL,應用程式必須具有 Policy 的實作。

Policy 介面會宣告兩種方法 (allowAccess()processServerResponse()),在處理授權伺服器的回應時,LicenseChecker 執行個體會呼叫這些方法。它還會宣告一個稱作 LicenseResponse 的列舉,用於指定在呼叫 processServerResponse() 時傳遞的授權回應值。

  • processServerResponse() 可讓您預先處理從授權伺服器接收的原始回應資料,然後再決定是否授予存取權。

    一般實作作業會從授權回應中擷取部分或全部欄位,然後將資料儲存在本機的永久儲存空間 (例如透過 SharedPreferences 儲存空間),以確保在應用程式叫用和裝置重新開機時可以存取這些資料。例如,Policy 會在永久的儲存空間中保留最近一次成功授權檢查的時間戳記、重試次數、授權有效期,以及類似的資訊,而不是每次啟用應用程式時都重設這些值。

    若是將回應資料儲存在本機,Policy 必須確保資料經過模糊處理 (請參閱下方的實作模糊處理工具)。

  • allowAccess() 會根據任何可用的授權回應資料 (來自授權伺服器或快取) 或其他應用程式的特定資訊,決定是否授予使用者應用程式的存取權。舉例來說,實作 allowAccess() 時可能會將其他條件納入考量,例如使用情況或其他從後端伺服器擷取的資料。在所有情況下,如果根據授權伺服器的判定結果授權使用者使用應用程式,或者如果出現暫時性網路或系統問題導致授權檢查無法完成,實作 allowAccess() 時應僅能傳回 true。在這類情況下,實作可以紀錄重試回應的次數,並暫時允許存取,直到下次授權檢查完成為止。

為了簡化為應用程式新增授權的流程並説明應如何設計 Policy,LVL 包含兩個完整的 Policy 實作項目,您可以不經修改直接使用,或者視需要進行調整。

  • ServerManagedPolicy,一個具有彈性的 Policy,使用伺服器提供的設定和已快取過的回應來管理不同網路狀況下的存取權,以及
  • StrictPolicy,不會快取任何回應資料,並且在伺服器傳回授權回應時才允許存取。

我們強烈建議大多數應用程式都採用 ServerManagedPolicy。ServerManagedPolicy 是 LVL 的預設,與 LVL 範例應用程式整合。

自訂政策規範

在實作授權時,您可以使用 LVL 中提供的完整政策之一 (ServerManagedPolicy 或 StrictPolicy),也可以建立一個自訂政策。無論是哪種類型的自訂政策,您必須瞭解幾個重要的設計點並在實作中納入考量。

授權伺服器套用一般的要求限制,以防止過度使用資源導致阻斷服務。當應用程式超過要求限制時,授權伺服器會傳回 503 回應,以一般的伺服器錯誤傳遞至應用程式。這表示在重設限制之前,使用者不會收到任何授權回應,因而可能會造成使用者無限期的影響。

若要設計自訂的政策,建議您使用 Policy

  1. 在本機永久儲存空間中快取 (並妥善模糊處理) 最近成功的授權回應。
  2. 只要快取的回應仍然有效,就對所有授權檢查傳回快取回應,而不是向授權伺服器提出要求。強烈建議您根據伺服器提供的 VT 額外項目設定回應的有效性。詳情請參閱伺服器回應額外項目
  3. 如果重試任何要求會導致錯誤,請以指數輪詢的方式建立週期。請注意,Google Play 用戶端會自動重試失敗的要求,因此在大多數情況下,Policy 無須重試這些要求。
  4. 提供「寬限期」,讓使用者在有限的時間或使用次數內存取應用程式,同時重試授權檢查。藉由以下,寬限期可讓使用者獲得好處:在成功完成下次授權檢查爲止之前,允許使用者存取;這也讓您獲得好處:在不具備有效的授權回應下,可對應用程式的存取權施加硬性限制。

請務必根據上述規範設計 Policy,因為這個規範可盡可能確保使用者享有最佳體驗,同時即使在錯誤的狀況下,您也能有效控制應用程式。

請注意,所有 Policy 皆可使用授權伺服器提供的設定,協助管理有效性和快取、重試的寬限期等。擷取伺服器提供的設定非常簡單,強烈建議您充分利用這些設定。如需關於如何擷取並使用額外項目的範例,請參閱 ServerManagedPolicy 實作。如需伺服器設定清單及其使用方式的相關資訊,請參閱伺服器回應額外項目

ServerManagedPolicy

LVL 包含 Policy 介面 (稱爲 ServerManagedPolicy) 的完整、建議的實作項目。此實作項目與 LVL 類別整合,可做為程式庫中的預設 Policy

ServerManagedPolicy 負責處理所有的授權和回應重試。它將所有回應資料快取到本機的 SharedPreferences 檔案中,並透過應用程式的 Obfuscator 實作對其進行模糊處理。這可以確保授權回應資料安全無虞,且在裝置重新開機後永久保留這些資料。ServerManagedPolicy 提供介面方法 processServerResponse()allowAccess() 的具體實作,並提供一組協助用於管理授權回應的方法和類型。

重要的是,ServerManagedPolicy 的其中一個關鍵功能是使用伺服器提供的設定,並以此為基礎,在應用程式退款期內以及各種不同的網路和錯誤狀況下管理授權。當應用程式與 Google Play 伺服器通訊進行授權檢查時,伺服器會附加多個設定,做爲特定授權回應類型的額外項目欄位中的鍵/值組合。例如,伺服器針對應用程式的授權有效期、重試寬限期、允許重試次數上限等提供建議值。ServerManagedPolicy 利用 processServerResponse() 方法從授權回應中擷取這些值,並使用 allowAccess() 方法檢查。如需 ServerManagedPolicy 使用的伺服器提供設定清單,請參閱伺服器回應額外項目

為了方便、達到最佳效能,並享有使用 Google Play 伺服器的授權設定帶來的好處,强烈建議您使用 ServerManagedPolicy 做為授權 Policy

如果擔心儲存在本機的 SharedPreferences 中授權回應資料的安全性,可以使用更强大的模糊處理演算法,或設計不會儲存授權資料且更嚴格的 Policy。LVL 包含這類 Policy 的範例 — 詳情請參閱StrictPolicy

如要使用 ServerManagedPolicy,只需將其匯入 Activity、建立執行個體,然後在建構 LicenseChecker 時將參照傳遞至執行個體即可。詳情請參閱對 LicenseChecker 和 LicenseCheckerCallback 執行個體化

StrictPolicy

LVL 具有另一個稱爲 StrictPolicy 的 Policy 介面完整實作。StrictPolicy 所提供的政策比 ServerManagedPolicy 更加嚴格,因為它不允許使用者存取應用程式,除非在存取時從伺服器收到的授權回應表示使用者已獲得授權。

StrictPolicy 的主要功能在於,它不會將任何授權回應資料儲存在本機的永久儲存空間中。由於未儲存任何資料,因此系統不會追蹤重試要求,也無法使用快取回應執行授權檢查。Policy 僅在下列情況中允許存取:

  • 收到授權伺服器的授權回應。
  • 授權回應表示使用者已獲得授權,可存取應用程式。

如果您關心的主要問題在於,除非確認使用者在使用時已獲得授權,否則在任何可能的情況下都不允許使用者存取應用程式,則您適合使用 StrictPolicy。此外,該政策提供的安全性略高於 ServerManagedPolicy — 因為本機沒有快取任何資料,所以惡意使用者無法竄改快取資料並取得應用程式的存取權。

同時,此 Policy 對一般使用者來說會有一些問題,因為如果沒有可用的網路 (行動數據或 Wi-Fi) 連線,使用者將無法存取應用程式。另一個副作用是,應用程式會向伺服器傳送更多的授權檢查要求,因為它們無法使用快取回應。

整體而言,這項政策是在一定程度上為使用者帶來便利,以及提供絕對安全性和存取權控制之間的取捨。使用此 Policy 前,請先審慎思考如何取捨。

若要使用 StrictPolicy,請直接將其匯入 Activity、建立執行個體,然後在建構 LicenseChecker 時傳送參照。詳情請參閱對 LicenseChecker 和 LicenseCheckerCallback 執行個體化

一般的 Policy 實作必須將應用程式的授權回應資料儲存至永久儲存空間,確保在應用程式叫用和裝置重新開機時可以存取這些資料。例如,Policy 會保留最近一次成功授權檢查的時間戳記、重試次數、授權有效期,以及永久儲存空間中的類似資訊,而不是每次啟用應用程式時都要重設這些值。LVL 中包含的預設 Policy (ServerManagedPolicy) 會將授權回應資料儲存在 SharedPreferences 執行個體中,以確保資料會永久留存。

由於 Policy 會使用已儲存的授權回應資料來決定是允許還是禁止使用者存取應用程式,因此必須確保儲存的所有資料安全無虞,同時裝置上的超級使用者無法重複使用或操控這些資料。具體而言,Policy 必須先使用應用程式和裝置的特有金鑰對資料進行模糊處理,然後才能儲存這些資料。請務必使用應用程式特定和裝置特定的金鑰進行模糊處理,因為這樣可以防止在應用程式和裝置中分享經過模糊處理的資料。

LVL 可協助應用程式以安全、持續的方式儲存授權回應資料。首先,它提供了一個 Obfuscator 介面,使應用程式可以為儲存的資料提供所選的模糊處理演算法。以此為基礎,LVL 提供輔助類別 PreferenceOjpegor,用來處理大部分呼叫應用程式的 Obfuscator 類別以及讀取和寫入 SharedPreferences 執行個體中經模糊處理資料的作業。

LVL 提供一個稱爲 AESObfuscator 的完整 Obfuscator 實作,其使用了 AES 加密功能來模糊處理資料。您可以在應用程式中使用 AESObfuscator,無須進行修改,或者視需要進行調整。如果您使用會快取授權回應資料的 Policy (例如 ServerManagedPolicy),強烈建議您使用 AESObfuscator 做為 Obfuscator 實作的基礎。詳情請參閱下一節。

AESObfuscator

LVL 包含一個 Obfuscator 介面 (稱爲 AESObfuscator),為完整且建議的實作項目。此實作項目與 LVL 範例應用程式整合,並做為程式庫中的預設 Obfuscator

AESObfuscator 使用 AES 在將資料寫入儲存空間或從儲存空間讀取資料時進行加密和解密,透過安全的方式對資料進行模糊處理。Obfuscator使用應用程式提供的三個資料欄位執行加密程序:

  1. Salt — 每次 (非) 模糊處理作業所使用的隨機位元組陣列。
  2. 應用程式 ID 字串,通常是應用程式的套件名稱。
  3. 裝置 ID 字串,來自盡可能多的裝置特定來源,以確定不會重複。

若要使用 AESObfuscator,請先將其匯入 Activity。宣告一個用來保存 salt 位元組的靜態最終陣列,並將其初始化為隨機產生的 20 個位元組。

Kotlin

// Generate 20 random bytes, and put them here.
private val SALT = byteArrayOf(
        -46, 65, 30, -128, -103, -57, 74, -64, 51, 88,
        -95, -45, 77, -117, -36, -113, -11, 32, -64, 89
)

Java

...
    // Generate 20 random bytes, and put them here.
    private static final byte[] SALT = new byte[] {
     -46, 65, 30, -128, -103, -57, 74, -64, 51, 88, -95,
     -45, 77, -117, -36, -113, -11, 32, -64, 89
     };
    ...

接著,宣告一個變數來存放裝置 ID,並以必要的方式爲其產生值。例如,LVL 中包含的範例應用程式會查詢 android.Settings.Secure.ANDROID_ID 的系統設定,而每個裝置都有各自的專屬設定。

請注意,視您使用的 API 而定,應用程式可能需要要求其他權限,才能取得裝置特定資訊。例如,若要查詢 TelephonyManager 以取得裝置 IMEI 或相關資料,應用程式還必須在資訊清單中要求 android.permission.READ_PHONE_STATE 的權限。

純粹爲了取得在 Obfuscator 中使用的裝置特定資訊而要求新權限之前,請考慮這麼做可能會對應用程式或其在 Google Play 上的篩選條件產生的影響 (因為某些權限可能會導致 SDK 建構工具新增相關的 <uses-feature>)。

最後,建構一個 AESObfuscator 的執行個體,並傳送salt、應用程式 ID 和裝置 ID。您可以在建構 PolicyLicenseChecker 時,直接建構執行個體。例如:

Kotlin

    ...
    // Construct the LicenseChecker with a Policy.
    private val checker = LicenseChecker(
            this,
            ServerManagedPolicy(this, AESObfuscator(SALT, packageName, deviceId)),
            BASE64_PUBLIC_KEY
    )
    ...

Java

    ...
    // Construct the LicenseChecker with a Policy.
    checker = new LicenseChecker(
        this, new ServerManagedPolicy(this,
            new AESObfuscator(SALT, getPackageName(), deviceId)),
        BASE64_PUBLIC_KEY // Your public licensing key.
        );
    ...

如需完整範例,請參閱 LVL 範例應用程式中的 MainActivity。

檢查 Activity 中的授權

完成 Policy 的實作以管理應用程式的存取權後,下一步就是為應用程式新增授權檢查,它會視需要向授權伺服器進行查詢,並根據授權的回應管理應用程式的存取權。所有有關新增授權檢查和處理回應的作業,都在主要的 Activity 原始檔中執行。

若要新增授權檢查並處理回應,您必須:

  1. 新增匯入作業
  2. 以私人內部類別的方式實作 LicenseCheckerCallback
  3. 建立一個處理常式,用來從 LicenseCheckerCallback 發布至 UI 執行緒
  4. 對 LicenseChecker 和 LicenseCheckerCallback 執行個體化
  5. 呼叫 checkAccess() 以啟動授權檢查
  6. 嵌入授權用的公開金鑰
  7. 呼叫 LicenseChecker 的 onDestroy() 方法以關閉 IPC 連線。

下列章節詳細說明了這些工作。

授權檢查和回應總覽

在多數情況下,您應使用 onCreate() 方法,將授權檢查新增至應用程式的主要 Activity 中。這樣可確保使用者直接啟動應用程式時,系統會立即叫用授權檢查。在某些情況下,您也可以在其他位置新增授權檢查。舉例來說,如果應用程式包含其他應用程式可透過 Intent 啟動的多個 Activity 元件,您可以在這些 Activity 中新增授權檢查。

授權檢查包含兩項主要操作:

  • 呼叫用於啟動授權檢查的方法 — 在 LVL 中,這就是向您建構的 LicenseChecker 物件的 checkAccess() 方法進行呼叫。
  • 傳回授權檢查結果的回呼。在 LVL 中,這就是您實作的 LicenseCheckerCallback 介面。介面會宣告兩種方法 (allow()dontAllow()),程式庫根據授權檢查結果來叫用這些方法。您依您所需的邏輯實作這兩種方法,以允許或禁止使用者存取應用程式。請注意,這些方法無法確定是否允許存取存取,必須由您識做的 Policy 來判斷。相反地,這些方法只是提供如何允許和禁止存取的應用程式行為 (以及處理應用程式錯誤)。

    allow()dontAllow() 方法的確會提供回應的「原因」,可以是其中一個 Policy 值、LICENSEDNOT_LICENSEDRETRY。另外,您應處理方法收到 dontAllow()RETRY 回應的情況,並為使用者提供「重試」按鈕 (這可能是因為在要求時服務無法使用)。

圖 1. 一般授權檢查互動總覽。

上圖顯示一般授權檢查的執行方式:

  1. 應用程式的主 Activity 中的程式碼對 LicenseCheckerCallbackLicenseChecker 物件執行個體化。建構 LicenseChecker 時,程式碼會傳遞 Context (為要使用的 Policy 實作),以及用來授權的發布者帳戶公開金鑰以作為參數。
  2. 然後,程式碼呼叫 LicenseChecker 物件的 checkAccess() 方法。這個方法會呼叫 Policy,以判斷 SharedPreferences 中是否存在本機快取的有效授權回應。
    • 如果存在,checkAccess() 會呼叫 allow()
    • 否則,LicenseChecker 會啟動傳送至授權伺服器的授權檢查要求。

    注意:為草稿版應用程式執行授權檢查時,授權伺服器一律會傳回 LICENSED

  3. 收到回應後,LicenseChecker 會建立 LicenseValidator 來驗證已簽署的授權資料,並擷取回應欄位,接著將這些資料傳送至 Policy 以進行進一步評估。
    • 如果授權有效,Policy 會將回應快取在 SharedPreferences 中並通知驗證工具,該工具會呼叫 LicenseCheckerCallback 物件的 allow() 方法。
    • 如果授權無效,Policy 會通知驗證工具,該工具會呼叫 LicenseCheckerCallbackdontAllow() 方法。
  4. 如果發生可復原的本機或伺服器錯誤(例如網路無法傳送要求),LicenseChecker 會將 RETRY 回應傳遞至 Policy 物件的 processServerResponse() 方法。

    此外,allow()dontAllow() 回呼方法都會收到一個 reason 引數。allow() 方法的原因通常是 Policy.LICENSEDPolicy.RETRYdontAllow() 原因通常是 Policy.NOT_LICENSEDPolicy.RETRY。這些回應值相當實用,您可以用來向使用者顯示適當的回應,例如當 dontAllow() 回應 Policy.RETRY 時 (這可能是因爲服務無法使用),則提供「重試」按鈕。

  5. 如果發生應用程式錯誤 (例如應用程式嘗試檢查一個無效套件名稱的授權),LicenseChecker 會將錯誤回應傳遞給 LicenseCheckerCallback 的 applicationError() 方法。

請注意,除了以下各節所述的啟動授權檢查並處理結果之外,應用程式還必須具備政策的實作內容,而且如果 Policy 儲存了回應資料 (例如 ServerManagedPolicy),也必須有模糊處理工具的實作。

新增匯入作業

首先,開啟應用程式的主 Activity 類別檔案,然後從 LVL 套件匯入 LicenseCheckerLicenseCheckerCallback

Kotlin

import com.google.android.vending.licensing.LicenseChecker
import com.google.android.vending.licensing.LicenseCheckerCallback

Java

import com.google.android.vending.licensing.LicenseChecker;
import com.google.android.vending.licensing.LicenseCheckerCallback;

如果使用 LVL 提供的預設 Policy 實作項目 (ServerManagedPolicy),請隨 AESObfuscator 一併匯入。如果您使用自訂的 PolicyObfuscator,請改匯入這些項目。

Kotlin

import com.google.android.vending.licensing.ServerManagedPolicy
import com.google.android.vending.licensing.AESObfuscator

Java

import com.google.android.vending.licensing.ServerManagedPolicy;
import com.google.android.vending.licensing.AESObfuscator;

以私人內部類別的方式實作 LicenseCheckerCallback

LicenseCheckerCallback 是 LVL 提供的介面,用於處理授權檢查的結果。若要支援使用 LVL 進行授權,您必須實作 LicenseCheckerCallback 及其方法,以允許或禁止存取應用程式。

授權檢查的結果一律為 LicenseCheckerCallback 方法的其中一種呼叫,其為根據回應酬載的驗證結果、伺服器回應代碼本身以及 Policy 提供的任何其他處理所進行的呼叫。應用程式可透過任何所需的方式實作這些方法。一般來說,建議盡量將方法簡化,將其限定於僅能用於管理 UI 狀態和應用程式存取權。如果您想進一步處理授權回應 (例如與後端伺服器通訊或套用自訂限制),應考慮將 Policy 納入程式碼,而不是將其放入 LicenseCheckerCallback 方法。

在大多數情況下,您應在應用程式的主 Activity 類別中,將 LicenseCheckerCallback 實作宣告為私人類別。

視需要實作 allow()dontAllow() 方法。首先,您可以在這些方法中運用簡單的結果處理,例如在對話方塊中顯示授權結果。這可讓應用程式執行地更快,並可協助偵錯。稍後,決定要採取的行為後,您可以新增更複雜的處理。

一些針對處理 dontAllow() 中未經授權的回應建議包括:

  • 向使用者顯示「再試一次」對話方塊,其中包含一個按鈕,會在提供的 reasonPolicy.RETRY 時啟動新的授權檢查。
  • 顯示「購買這個應用程式」對話方塊,其中包含一個按鈕,可將使用者深層連結至 Google Play 中的應用程式詳細資料頁面,使用者可以在此頁面中購買應用程式。如要進一步瞭解如何設定此類連結,請參閱連結至您的產品
  • 顯示一則浮動式訊息通知,表示應用程式的功能因未獲得授權而受到限制。

以下範例說明 LVL 範例應用程式如何實作 LicenseCheckerCallback,以及在對話方塊中顯示授權檢查結果的方法。

Kotlin

private inner class MyLicenseCheckerCallback : LicenseCheckerCallback {

    override fun allow(reason: Int) {
        if (isFinishing) {
            // Don't update UI if Activity is finishing.
            return
        }
        // Should allow user access.
        displayResult(getString(R.string.allow))
    }

    override fun dontAllow(reason: Int) {
        if (isFinishing) {
            // Don't update UI if Activity is finishing.
            return
        }
        displayResult(getString(R.string.dont_allow))

        if (reason == Policy.RETRY) {
            // If the reason received from the policy is RETRY, it was probably
            // due to a loss of connection with the service, so we should give the
            // user a chance to retry. So show a dialog to retry.
            showDialog(DIALOG_RETRY)
        } else {
            // Otherwise, the user isn't licensed to use this app.
            // Your response should always inform the user that the application
            // isn't licensed, but your behavior at that point can vary. You might
            // provide the user a limited access version of your app or you can
            // take them to Google Play to purchase the app.
            showDialog(DIALOG_GOTOMARKET)
        }
    }
}

Java

private class MyLicenseCheckerCallback implements LicenseCheckerCallback {
    public void allow(int reason) {
        if (isFinishing()) {
            // Don't update UI if Activity is finishing.
            return;
        }
        // Should allow user access.
        displayResult(getString(R.string.allow));
    }

    public void dontAllow(int reason) {
        if (isFinishing()) {
            // Don't update UI if Activity is finishing.
            return;
        }
        displayResult(getString(R.string.dont_allow));

        if (reason == Policy.RETRY) {
            // If the reason received from the policy is RETRY, it was probably
            // due to a loss of connection with the service, so we should give the
            // user a chance to retry. So show a dialog to retry.
            showDialog(DIALOG_RETRY);
        } else {
            // Otherwise, the user isn't licensed to use this app.
            // Your response should always inform the user that the application
            // isn't licensed, but your behavior at that point can vary. You might
            // provide the user a limited access version of your app or you can
            // take them to Google Play to purchase the app.
            showDialog(DIALOG_GOTOMARKET);
        }
    }
}

此外,還應實作 applicationError() 方法,LVL 會呼叫此方法,讓應用程式處理無法重試的錯誤。如需此類錯誤的清單,請參閱授權參考資料中的伺服器回應代碼。您可透過任何所需的方式實作這些方法。在多數情況下,此方法應記錄錯誤代碼並呼叫 dontAllow()

建立可從 LicenseCheckerCallback 發布至 UI 執行緒的處理常式

在授權檢查期間,LVL 會將要求傳遞至 Google Play 應用程式,以便處理與授權伺服器之間的通訊。LVL 會透過非同步 IPC (使用 Binder) 傳遞要求,因此實際的處理和網路通訊並不會在應用程式管理的執行緒中進行。同樣地,Google Play 應用程式收到結果時,也會透過 IPC 叫用回呼方法,接著在應用程式處理程序的 IPC 執行緒池中執行該方法。

LicenseChecker 類別管理應用程式與 Google Play 應用程式的 IPC 通訊,包括傳送要求的呼叫和接收回應的回呼。LicenseChecker 還會追蹤開放式的授權要求並管理逾時。

爲了能夠正確處理逾時並處理收到的回應,且不會影響應用程式的 UI 執行緒,LicenseChecker 會在執行個體化時產生背景執行緒。執行緒會處理所有授權檢查結果,無論結果是伺服器傳回的回應還是逾時錯誤。處理結束後,LVL 會從背景執行緒呼叫 LicenseCheckerCallback 方法。

對應用程式而言,這意味著:

  1. 在許多情況下,系統會從背景執行緒叫用 LicenseCheckerCallback 方法。
  2. 除非您在 UI 執行緒中建立一個處理常式,並將回呼方法發布至處理常式,否則這些方法將無法更新狀態或叫用 UI 執行緒中的任何處理功能。

如果要使用 LicenseCheckerCallback 方法更新 UI 執行緒,請將主 Activity 的 onCreate() 方法對 Handler 執行個體化,如下所示。在此範例中,LVL 範例應用程式的 LicenseCheckerCallback 方法 (如上所示) 會呼叫 displayResult(),以透過處理常式的 post() 方法更新 UI 執行緒。

Kotlin

    private lateinit var handler: Handler

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        handler = Handler()
    }

Java

    private Handler handler;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        ...
        handler = new Handler();
    }

接著,在 LicenseCheckerCallback 方法中,您可以使用處理常式方法將 Runnable 或 Message 物件發布到處理常式。下面介紹 LVL 中包含的範例應用程式如何在 UI 執行緒中將 Runnable 發布至處理常式以顯示授權狀態。

Kotlin

private fun displayResult(result: String) {
    handler.post {
        statusText.text = result
        setProgressBarIndeterminateVisibility(false)
        checkLicenseButton.isEnabled = true
    }
}

Java

private void displayResult(final String result) {
        handler.post(new Runnable() {
            public void run() {
                statusText.setText(result);
                setProgressBarIndeterminateVisibility(false);
                checkLicenseButton.setEnabled(true);
            }
        });
    }

對 LicenseChecker 和 LicenseCheckerCallback 執行個體化

在主 Activity 的 onCreate() 方法中,建立 LicenseCheckerCallback 和 LicenseChecker 的私人執行個體。您必須先對 LicenseCheckerCallback 執行個體化,因為當您呼叫 LicenseChecker 的建構函式時,必須傳遞一個參照給該執行個體。

LicenseChecker 執行個體化時,必須傳入以下參數:

  • 應用程式 Context
  • 用於授權檢查的 Policy 實作的參照。在大多數情況下,您應使用 LVL 提供的預設 Policy 實作項目 (ServerManagedPolicy)。
  • 保留用來授權的發布者帳戶公開金鑰的字串變數。

如果您使用 ServerManagedPolicy,則無須直接存取該類別,因此您可在 LicenseChecker 建構函式中對該類別執行個體化,如以下範例所示。請注意,建構 ServerManagedPolicy 時,您必須傳遞參照給新的模糊處理工具執行個體。

以下範例説明如何從 Activity 類別的 onCreate() 方法對 LicenseCheckerLicenseCheckerCallback 執行個體化。

Kotlin

class MainActivity : AppCompatActivity() {
    ...
    private lateinit var licenseCheckerCallback: LicenseCheckerCallback
    private lateinit var checker: LicenseChecker

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        // Construct the LicenseCheckerCallback. The library calls this when done.
        licenseCheckerCallback = MyLicenseCheckerCallback()

        // Construct the LicenseChecker with a Policy.
        checker = LicenseChecker(
                this,
                ServerManagedPolicy(this, AESObfuscator(SALT, packageName, deviceId)),
                BASE64_PUBLIC_KEY // Your public licensing key.
        )
        ...
    }
}

Java

public class MainActivity extends Activity {
    ...
    private LicenseCheckerCallback licenseCheckerCallback;
    private LicenseChecker checker;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // Construct the LicenseCheckerCallback. The library calls this when done.
        licenseCheckerCallback = new MyLicenseCheckerCallback();

        // Construct the LicenseChecker with a Policy.
        checker = new LicenseChecker(
            this, new ServerManagedPolicy(this,
                new AESObfuscator(SALT, getPackageName(), deviceId)),
            BASE64_PUBLIC_KEY // Your public licensing key.
            );
        ...
    }
}

請注意,只有當本機具有有效的授權回應時,LicenseChecker 才會透過 UI 執行緒呼叫 LicenseCheckerCallback 方法。如果將授權檢查傳送至伺服器,則回呼一律來自背景執行緒,即使發生網路錯誤亦是如此。

呼叫 checkAccess() 以啟動授權檢查

在主 Activity中,新增對 LicenseChecker 執行個體的 checkAccess() 方法的呼叫。在呼叫中,將參照做為參數傳遞給 LicenseCheckerCallback 執行個體。如果您需要在呼叫之前處理任何特殊的 UI 效果或狀態管理,實用的做法是使用包裝函式方法呼叫 checkAccess()。例如,LVL 範例應用程式使用 doCheck() 包裝函式方法呼叫 checkAccess()

Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        // Call a wrapper method that initiates the license check
        doCheck()
        ...
    }
    ...
    private fun doCheck() {
        checkLicenseButton.isEnabled = false
        setProgressBarIndeterminateVisibility(true)
        statusText.setText(R.string.checking_license)
        checker.checkAccess(licenseCheckerCallback)
    }

Java

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // Call a wrapper method that initiates the license check
        doCheck();
        ...
    }
    ...
    private void doCheck() {
        checkLicenseButton.setEnabled(false);
        setProgressBarIndeterminateVisibility(true);
        statusText.setText(R.string.checking_license);
        checker.checkAccess(licenseCheckerCallback);
    }

嵌入授權用的公開金鑰

Google Play 服務會自動為每個應用程式產生 2048 位元的 RSA 公開/私密金鑰組,將其用於授權和應用程式內結帳。此金鑰組與應用程式之間具有唯一的對應關係。雖然與應用程式相關,但此金鑰組不同於您在簽署應用程式 (或從應用程式中衍生) 時使用的金鑰。

Google Play 管理中心會向登入 Play 管理中心的開發人員公開公開金鑰,但私密金鑰則存放在所有使用者看不到的安全位置。當應用程式針對您帳戶中發布的應用程式要求授權檢查時,授權伺服器會使用應用程式金鑰組的私密金鑰簽署授權回應。LVL 收到回應後,會使用應用程式提供的公開金鑰來驗證授權回應的簽名。

若要為應用程式新增授權,您必須取得應用程式用於授權的公開金鑰,然後將該金鑰複製到應用程式中。以下說明如何找出應用程式的公開授權金鑰:

  1. 前往 Google Play 管理中心並登入帳戶。請確認您登入的帳戶,是您用來發布 (或將要發布) 而正要授權的應用程式的帳戶。
  2. 在應用程式詳細資料頁面中,找到服務與 API連結,然後按一下。
  3. 在「服務與 API」頁面中,找到「授權與應用程式內結帳」部分。公開授權金鑰顯示在「此應用程式的授權金鑰」的欄位中。

若要將公開金鑰新增至應用程式,只須複製欄位中的金鑰字串,然後貼到應用程式中做爲字串變數 BASE64_PUBLIC_KEY 的值即可。複製時,請務必選取整個金鑰字串,不要遺漏任何字元。

以下是 LVL 範例應用程式的範例:

Kotlin

private const val BASE64_PUBLIC_KEY = "MIIBIjANBgkqhkiG ... " //truncated for this example
class LicensingActivity : AppCompatActivity() {
    ...
}

Java

public class MainActivity extends Activity {
    private static final String BASE64_PUBLIC_KEY = "MIIBIjANBgkqhkiG ... "; //truncated for this example
    ...
}

呼叫 LicenseChecker 的 onDestroy() 方法以關閉 IPC 連線

最後,若要在應用程式 Context 變更前清理 LVL,請從 Activity 的 onDestroy() 實作中呼叫 LicenseCheckeronDestroy() 方法。這個呼叫可讓 LicenseChecker 正確關閉與 Google Play 應用程式的 ILicenseService 之間的任何公開 IPC 連線,並移除服務和處理常式所有在本機的參照。

如未能呼叫 LicenseCheckeronDestroy() 方法,可能導致應用程式的生命週期發生問題。例如,如果使用者在授權檢查期間變更螢幕方向,應用程式的 Context 就會遭到刪除。如果應用程式未正確關閉 LicenseChecker 的 IPC 連線,則收到回應時應用程式就會當機。同樣地,如果使用者在授權檢查進行期間離開應用程式,除非正確呼叫 LicenseCheckeronDestroy() 方法來中斷與服務的連線,否則收到回應時應用程式就會當機。

以下是 LVL 中包含的範例應用程式的範例,其中 mCheckerLicenseChecker 執行個體:

Kotlin

    override fun onDestroy() {
        super.onDestroy()
        checker.onDestroy()
        ...
    }

Java

    @Override
    protected void onDestroy() {
        super.onDestroy();
        checker.onDestroy();
        ...
    }

擴充或修改 LicenseChecker 時,您可能還必須呼叫 LicenseCheckerfinishCheck() 方法,以清除所有開啟的 IPC 連線。

實作 DeviceLimiter

在某些情況下,您可能想讓 Policy 限制可以使用單一授權的實際裝置數量。這樣可防止使用者將授權的應用程式移到多個裝置上,並以相同的帳戶 ID 在這些裝置上使用應用程式。還可以防止使用者透過以下方式「分享」應用程式:向其他使用者提供與該授權相關的帳戶資訊,然後這些使用者可以在自己的裝置上登入該帳戶,並存取應用程式的授權。

LVL 可支援對每個裝置的授權,方法為經由宣告單一的 allowDeviceAccess() 方法的 DeviceLimiter 介面,。LicenseValidator 處理來自授權伺服器的回應時,會呼叫 allowDeviceAccess(),並傳遞從回應中擷取的使用者 ID 字串。

如果您不想支援裝置的限制,則無須執行任何操作LicenseChecker 類別會自動使用一個稱爲 NullDeviceLimiter 的預設實作項目。顧名思義,NullDeviceLimiter 是「免管理」的類別,其 allowDeviceAccess() 方法只會傳回所有使用者和裝置的 LICENSED 回應。

注意:對大多數應用程式而言,不建議使用依裝置進行授權的方法,原因如下:

  • 您必須提供後端伺服器來管理使用者和裝置的對應,並且
  • 這可能會無意中導致使用者在存取他們在其他裝置上合法購買的應用程式時遭到拒絕。

模糊處理程式碼

為確保應用程式的安全性 (尤其是使用授權和/或自訂限制和保護措施的付費應用程式),請務必模糊處理應用程式的程式碼。對程式碼進行適當的模糊處理,可讓惡意使用者更難反編譯應用程式的位元碼,進行修改 (例如移除授權檢查) 後再重新編譯。

Android 應用程式可使用幾種模糊處理工具程式,其中包括 ProGuard,此程式也提供了程式碼最佳化功能。對於使用 Google Play 授權的所有應用程式,强烈建議使用 ProGuard 或類似的程式對程式碼進行模糊處理。

發布授權應用程式

完成授權實作的測試後,即可準備在 Google Play 上發布應用程式。請按照一般步驟準備簽署,然後發布應用程式

哪裡可以取得支援服務

如果在應用程式中實作或部署發布時有任何疑問或遇到問題,請使用下表所列的支援資源。在適當的論壇提出您的問題,可更快獲得支援。

表 2.Google Play 授權服務的開發人員支援資源。

支援類型 資源 主題範圍
開發與測試問題 Google 網路論壇:android-developers LVL 下載與整合、程式庫專案、Policy 問題、使用者體驗的想法、回應處理、Obfuscator、IPC、測試環境設定
Stack Overflow:http://stackoverflow.com/questions/tagged/android
帳戶、發布和部署問題 Google Play 說明論壇 發布者帳戶、授權金鑰組、測試帳戶、伺服器回應、測試回應、應用程式部署作業及結果
市場授權支援常見問題
LVL Issue Tracker Marketlicensing 專案問題追蹤 特別針對與 LVL 原始碼類別和介面實作相關的錯誤和問題報告

如要瞭解如何在上述群組清單中張貼文章,請參閱「開發人員支援資源」頁面的社群資源一節。

其他資源

LVL 中包含的範例應用程式提供了完整的範例,說明如何在 MainActivity 類別中啟動授權檢查並處理結果。