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

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

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

設定發布者帳戶和開發環境後 (請參閱「設定授權」),即可使用授權驗證庫 (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 包含名為 ServerManagedPolicy 的 Policy 介面,這是建議採用的完整實作項目。此實作項目與 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 包含名爲 AESObfuscator Obfuscator 介面,這是建議採用的完整實作項目。此實作項目與 LVL 範例應用程式整合,並做為程式庫中的預設 Obfuscator

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

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

若要使用 AESObfuscator,請先將其匯入 Activity。請宣告用來保存鹽位元組的靜態最終陣列,並將其初始化為隨機產生的 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 中應用程式詳細資料頁面的深層連結按鈕,讓使用者在 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 執行個體化

在主要活動的 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 類別中啟動授權檢查並處理結果。