Ajouter la vérification des licences côté client à votre application

Avertissement : Lorsque votre application lance la procédure de validation des licences côté client, les potentiels pirates informatiques peuvent plus facilement modifier ou supprimer la logique associée à cette procédure de validation.

C'est pourquoi nous vous encourageons vivement à valider vos licences côté serveur.

Après avoir configuré un compte d'éditeur et un environnement de développement (consultez Configurer la gestion des licences), vous pouvez ajouter la validation des licences à votre application à l'aide de la bibliothèque LVL (License Verification Library).

Pour ajouter une vérification des licences avec la bibliothèque LVL vous devez :

  1. ajouter l'autorisation de licence au fichier manifeste de votre application ;
  2. implémenter une règle. Vous pouvez choisir l'une des implémentations complètes fournies dans la bibliothèque LVL ou créer la vôtre ;
  3. implémenter un offuscateur si votre Policy met en cache les données de réponse de licence ;
  4. ajouter du code pour vérifier la licence dans l'activité principale de votre application ;
  5. implémenter un DeviceLimiter (facultatif et déconseillé pour la plupart des applications).

Les sections ci-dessous détaillent les étapes précédemment citées. Une fois l'intégration terminée, vous devriez pouvoir compiler votre application et commencer à effectuer des tests, comme décrit dans la section configurer l'environnement de test.

Pour obtenir une présentation de l'ensemble complet des fichiers sources inclus dans la bibliothèque LVL, consultez la page résumé des classes et des interfaces de la bibliothèque LVL.

Ajouter l'autorisation de licence

Pour envoyer une vérification de licence au serveur à l'aide de l'application Google Play, celle-ci doit demander l'autorisation appropriée, com.android.vending.CHECK_LICENSE. Si votre application ne déclare pas l'autorisation de licence, mais tente de lancer une vérification de licence, la bibliothèque LVL génère une exception de sécurité.

Pour demander l'autorisation de licence dans votre application, déclarez un élément <uses-permission> en tant qu'enfant de l'élément <manifest> comme suit :

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

Par exemple, voici comment l'application exemple de la bibliothèque LVL déclare l'autorisation :

<?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>

Remarque : Pour le moment, vous ne pouvez pas déclarer l'autorisation CHECK_LICENSE dans le fichier manifeste de la bibliothèque LVL du projet, car les SDK Tools ne la fusionneront pas avec les fichiers manifestes des applications dépendantes. Vous devez donc déclarer l'autorisation dans le fichier manifeste de chaque application dépendante.

Implémenter une stratégie

Le service de gestion des licences de Google Play ne détermine pas lui-même si un utilisateur donné avec une licence donnée doit avoir accès à votre application. Cette responsabilité incombe plutôt à une implémentation Policy que vous faites dans votre application.

Policy est une interface déclarée par la bibliothèque LVL conçue pour contenir la logique de votre application pour autoriser ou interdire l'accès des utilisateurs, en fonction du résultat de la vérification des licences. Pour utiliser la bibliothèque LVL, votre application doit fournir une implémentation de Policy.

L'interface Policy déclare deux méthodes, allowAccess() et processServerResponse(), qui sont appelées par une instance LicenseChecker lors du traitement d'une réponse du serveur de licences. L'interface déclare également une énumération nommée LicenseResponse, qui spécifie la valeur de réponse de la licence transmise lors des appels à processServerResponse().

  • processServerResponse() vous permet de prétraiter les données de réponse brutes reçues du serveur de gestion des licences, avant de déterminer si l'accès doit être accordé ou non.

    Une implémentation classique consisterait à extraire tout ou partie des champs de la réponse de licence et à stocker les données localement dans un stockage persistant, par exemple via le stockage SharedPreferences, pour garantir que les données sont accessibles lors des appels de l'application et cycles d'alimentation de l'appareil. Par exemple, une Policy (règle) préserverait le code temporel de la dernière vérification réussie de licence, du nombre de tentatives, de la période de validité de la licence et d'autres informations similaires dans un stockage persistant, plutôt que de réinitialiser les valeurs à chaque lancement de l'application.

    Lorsque vous stockez des données de réponse localement, Policy doit s'assurer que les données sont obscurcies (voir la section implémenter un offuscateur ci-dessous).

  • allowAccess() détermine si l'utilisateur doit être autorisé à accéder à votre application, en fonction des données de réponse de licence disponibles (à partir du serveur de gestion des licences ou du cache) ou à d'autres informations spécifiques à l'application. Par exemple, votre implémentation d'allowAccess() peut prendre en compte des critères supplémentaires, tels que l'utilisation ou d'autres données récupérées sur un serveur backend. Dans tous les cas, une implémentation d'allowAccess() ne doit renvoyer true (vrai) que si l'utilisateur est autorisé à utiliser l'application, comme déterminé par le serveur de gestion des licences, ou en cas de problèmes transitoires liés au réseau ou au système et qui empêchent la vérification des licences. Dans ce cas, votre implémentation peut lister le nombre de tentatives de réponses et autoriser provisoirement l'accès jusqu'à ce que la prochaine vérification de licence soit terminée.

Pour simplifier le processus d'ajout de licence à votre application et montrer comment concevoir une Policy, la bibliothèque LVL comprend deux implémentations complètes de Policy, que vous pouvez utiliser telles quelles ou adapter à vos besoins :

  • ServerManagedPolicy, une Policy flexible qui utilise les paramètres fournis par le serveur et les réponses mises en cache pour gérer l'accès dans différentes conditions de réseau ; et
  • StrictPolicy, option qui ne met en cache aucune donnée de réponse et autorise l'accès uniquement si le serveur renvoie une réponse sous licence.

Pour la plupart des applications, il est vivement recommandé d'utiliser ServerManagedPolicy. ServerManagedPolicy est la bibliothèque LVL par défaut et est intégré à l'application exemple de la bibliothèque LVL.

Consignes concernant les règles personnalisées

Dans votre implémentation de la licence, vous pouvez utiliser l'une des règles complètes fournies dans la bibliothèque LVL (ServerManagedPolicy ou StrictPolicy) ou créer une règle personnalisée. Quel que soit le type de règle personnalisée, vous devez comprendre certains points importants et les prendre en compte lors de l'implémentation.

Le serveur de gestion des licences applique des limites de requêtes générales pour éviter toute utilisation excessive des ressources pouvant entraîner un déni de service. Lorsqu'une application dépasse la limite de requêtes, le serveur de gestion des licences renvoie une réponse 503, qui est transmise à votre application en tant qu'erreur générale du serveur. Cela signifie qu'aucune réponse de licence ne sera disponible pour l'utilisateur jusqu'à la réinitialisation de la limite, ce qui peut affecter l'utilisateur pendant une période indéfinie.

Si vous concevez une règle personnalisée, nous vous recommandons que Policy :

  1. Mette en cache (et obscurcisse correctement) la réponse de licence réussie la plus récente dans le stockage persistant local.
  2. Renvoie la réponse mise en cache pour toutes les vérifications de licence, tant que la réponse mise en cache est valide, au lieu d'envoyer une requête au serveur de gestion des licences. Il est vivement recommandé de définir la validité de la réponse en fonction du VT extra fourni par le serveur. Pour en savoir plus, consultez la section Réponses du serveur : extras.
  3. Utilise un intervalle exponentiel entre les tentatives. Si vous relancez une requête, cela génère des erreurs. Notez que le client Google Play relance automatiquement les requêtes ayant échoué. Dans la plupart des cas, il n'est donc pas nécessaire que votre Policy ne le fasse.
  4. Fournisse un "délai de grâce" permettant à l'utilisateur d'accéder à votre application pendant une durée limitée ou pendant un nombre limité d'utilisations, pendant qu'une vérification des licences est en cours. Le délai de grâce profite à l'utilisateur en autorisant l'accès jusqu'à la fin de la vérification des licences. Il vous permet en outre de limiter l'accès à votre application lorsqu'aucune réponse de licence valide n'est disponible.

Il est essentiel de concevoir votre Policy conformément aux indications ci-dessus, car elle garantit une expérience optimale aux utilisateurs tout en vous permettant de contrôler efficacement votre application, même en cas d'erreur.

Notez que toutes les Policy peuvent utiliser les paramètres fournis par le serveur de gestion des licences pour faciliter la gestion de la validité et de la mise en cache, relancer un délai de grâce, etc. L'extraction des paramètres fournis par le serveur est simple. Il est vivement recommandé de les utiliser. Reportez-vous à l'implémentation de ServerManagedPolicy pour obtenir un exemple illustrant comment extraire et utiliser les extras. Pour obtenir la liste des paramètres du serveur et savoir comment les utiliser, consultez la section Réponses du serveur : extras.

ServerManagedPolicy

Ce fichier comprend une implémentation complète et recommandée de l'interface dePolicy appelée ServerManagedPolicy. L'implémentation est intégrée aux classes de la bibliothèque LVL et sert de Policy par défaut dans la bibliothèque.

ServerManagedPolicy assure tout le traitement des réponses de licence et de relance. Il met en cache toutes les données de réponse localement dans un fichier SharedPreferences, en les obscurcissant avec l'implémentation de l'Obfuscator (offuscateur) de l'application. Cela garantit que les données de réponse de licence sont sécurisées et persistantes pendant les cycles d'alimentation de l'appareil. ServerManagedPolicy fournit des implémentations concrètes des méthodes d'interface processServerResponse() et allowAccess(), et inclut également un ensemble de méthodes et de types pris en charge pour gérer les réponses aux licences.

Il est important de noter que l'une des principales fonctionnalités de ServerManagedPolicy est l'utilisation de paramètres fournis par le serveur comme base pour la gestion des licences pendant la période de remboursement d'une application et dans des conditions de réseau instables et d'erreur. Lorsqu'une application contacte le serveur Google Play pour une vérification de licence, le serveur ajoute plusieurs paramètres en tant que paires clé/valeur dans le champ extras de certains types de réponses de licence. Par exemple, le serveur fournit, entre autres, des valeurs recommandées pour la période de validité de la licence de l'application, le délai de grâce pour les relances et le nombre maximal de relances. ServerManagedPolicy extrait les valeurs de la réponse de licence dans sa méthode processServerResponse() et les vérifie dans sa méthode allowAccess(). Pour obtenir la liste des paramètres fournis par le serveur utilisés par ServerManagedPolicy, consultez la section Réponses du serveur : extras.

Pour plus de commodité, des performances optimales et les avantages liés à l'utilisation des paramètres de licence du serveur Google Play, nous vous recommandons vivement d'utiliser ServerManagedPolicy en tant que Policy de licence.

Si vous êtes préoccupé par la sécurité des données de réponse de licence stockées localement dans SharedPreferences, vous pouvez utiliser un algorithme d'obscurcissement plus puissant ou concevoir une Policy plus stricte qui ne stocke pas de données de licence. La bibliothèque LVL comprend un exemple de Policy. Pour en savoir plus, consultez la section StrictPolicy.

Pour utiliser ServerManagedPolicy, il vous suffit de l'importer dans votre activité, de créer une instance et de transmettre une référence à celle-ci lors de la création de votre LicenseChecker. Pour en savoir plus, consultez Instancier LicenseChecker et LicenseCheckerCallback.

StrictPolicy

La bibliothèque LVL inclut une autre implémentation complète de l'interface Policy appelée StrictPolicy. L'implémentation de StrictPolicy fournit une règle plus restrictive que ServerManagedPolicy, en ce sens qu'elle ne permet pas à l'utilisateur d'accéder à l'application, sauf si le serveur reçoit une réponse de licence au moment de l'accès indiquant que l'utilisateur dispose d'une licence.

La fonctionnalité principale de StrictPolicy est qu'elle ne stocke aucune donnée de réponse de licence en local, dans un stockagepersistant. Étant donné qu'aucune donnée n'est stockée, les requêtes de nouvelle tentative ne sont pas suivies et les réponses mises en cache ne peuvent pas être utilisées pour effectuer des vérifications de licence. Policy n'autorise l'accès que si :

  • La réponse à la licence provient du serveur de gestion des licences, et
  • La réponse de la licence indique que l'utilisateur est autorisé à accéder à l'application.

L'utilisation de StrictPolicy est appropriée si votre principale préoccupation est de vous assurer, dans tous les cas possibles, qu'aucun utilisateur ne sera autorisé à accéder à l'application, sauf s'il est confirmé qu'il dispose d'une licence au moment de l'utilisation. En outre, StrictPolicy offre un niveau de sécurité légèrement supérieur à celui de ServerManagedPolicy, car aucune donnée n'est mise en cache localement. De ce fait, aucun utilisateur malveillant ne peut manipuler les données mises en cache et accéder à l'application.

En même temps, cette Policy pose problème aux utilisateurs normaux, car cela signifie qu'ils ne pourront pas accéder à l'application en l'absence de connexion réseau (cellulaire ou Wi-Fi) disponible. Un autre effet indirect est le fait que votre application enverra plus de requêtes de vérification de licence au serveur, car l'utilisation d'une réponse mise en cache n'est pas possible.

Dans l'ensemble, cette stratégie représente un compromis entre confort et sécurité absolue pour les utilisateurs et contrôle de leurs accès. Réfléchissez bien à ce compromis avant d'utiliser cette Policy.

Pour utiliser StrictPolicy, importez-la simplement dans votre activité, créez une instance et transmettez-lui une référence lorsque vous créez votre LicenseChecker. Pour en savoir plus, consultez Instancier LicenseChecker et LicenseCheckerCallback.

Une implémentation type de Policy doit enregistrer les données de réponse de licence d'une application dans un stockage persistant, afin qu'elles soient accessibles au fil des appels d'application et des cycles d'alimentation de l'appareil. Par exemple, une Policy (règle) préserverait le code temporel de la dernière vérification réussie de licence, du nombre de tentatives, de la période de validité de la licence et d'autres informations similaires dans un stockage persistant, plutôt que de réinitialiser les valeurs à chaque lancement de l'application. La Policy par défaut incluse dans la bibliothèque LVL, ServerManagedPolicy, stocke les données de réponse de licence dans une instance SharedPreferences, afin de garantir la persistance des données.

Étant donné que Policy utilisera les données de réponse de licence stockées pour déterminer si l'application doit être autorisée ou non, elle doit s'assurer que toutes les données stockées sont sécurisées et ne peuvent pas être réutilisées ou manipulées par un utilisateur racine sur un appareil. Plus précisément, la Policy doit toujours obscurcir les données avant de les stocker, à l'aide d'une clé unique pour l'application et l'appareil. Il est essentiel d'obscurcir à l'aide d'une clé spécifique à une application ou à un appareil, car cela évite que les données obscurcies soient partagées entre plusieurs applications et appareils.

La bibliothèque LVL aide l'application à stocker ses données de réponse de licence de manière sécurisée et persistante. Tout d'abord, elle fournit une interface Obfuscator qui permet à votre application de fournir l'algorithme d'obscurcissement de son choix pour les données stockées. Sur la base de celui-ci, la bibliothèque LVL fournit la classe PreferenceObfuscator, qui gère la plupart du travail d'appel de la classe Obfuscator de l'application, ainsi que de lecture et d'écriture des données obscurcies dans une instance SharedPreferences.

Cette bibliothèque offre une implémentation complète de Obfuscator appelée AESObfuscator, qui utilise le chiffrement AES pour obscurcir les données. Vous pouvez utiliser AESObfuscator dans votre application sans le modifier ou bien en l'adaptant à vos besoins. Si vous utilisez une Policy (telle que ServerManagedPolicy) qui met en cache les données de réponse de licence, nous vous recommandons vivement d'utiliser l'algorithme AESObfuscator pour votre implémentation de Obfuscator. Pour en savoir plus, consultez la section suivante.

AESObfuscator

Ce fichier comprend une implémentation complète et recommandée de l'interface d'Obfuscator appelée AESObfuscator. L'implémentation est intégrée à l'application exemple de la bibliothèque LVL et sert de Obfuscator par défaut dans la bibliothèque.

AESObfuscator fournit un obscurcissement sécurisé des données en utilisant AES pour chiffrer et déchiffrer les données au moment de leur écriture ou de leur lecture dans l'espace de stockage. L'Obfuscator partage le chiffrement à l'aide de trois champs de données fournis par l'application :

  1. Un salage : tableau d'octets aléatoires à utiliser pour chaque (dés)obscurcissement.
  2. Une chaîne d'identifiant d'application, généralement le nom de package de l'application.
  3. Une chaîne d'identifiant d'appareil, issue d'un maximum de sources propres à chaque appareil, afin de la rendre unique.

Pour utiliser AESObfuscator, commencez par l'importer dans votre activité. Déclarez un tableau final statique privé pour contenir les octets de salage et initialisez-le avec 20 octets générés de manière aléatoire.

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
     };
    ...

Ensuite, déclarez une variable destinée à contenir un identifiant d'appareil et à générer une valeur pour celle-ci, si nécessaire. Par exemple, l'exemple d'application inclus dans la bibliothèque LVL interroge les paramètres système pour l'android.Settings.Secure.ANDROID_ID, qui est unique à chaque appareil.

Notez qu'en fonction des API que vous utilisez, votre application devra peut-être demander des autorisations supplémentaires afin d'obtenir des informations spécifiques à l'appareil. Par exemple, pour interroger le TelephonyManager afin d'obtenir le code IMEI de l'appareil ou les données associées, l'application doit également demander l'autorisation android.permission.READ_PHONE_STATE dans son fichier manifeste.

Avant de demander de nouvelles autorisations dans le seul objectif d'acquérir des informations spécifiques à l'appareil pour votre Obfuscator, réfléchissez à la manière dont cela pourrait affecter votre application ou son filtrage sur Google Play. (car certaines autorisations peuvent entraîner l'ajout des <uses-feature> associés par les outils de compilation du SDK).

Enfin, construisez une instance d'AESObfuscator en transmettant le salage, l'identifiant d'application et l'identifiant d'appareil. Vous pouvez construire l'instance directement, tout en construisant Policy et LicenseChecker. Par exemple :

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.
        );
    ...

Pour obtenir un exemple complet, consultez MainActivity dans l'application exemple de la bibliothèque LVL.

Vérifier la licence associée à une activité

Une fois que vous avez implémenté une Policy pour gérer l'accès à votre application, l'étape suivante consiste à ajouter un contrôle de licence à votre application, qui lance une requête au serveur de gestion des licences si nécessaire et gère l'accès à l'application en fonction de la réponse obtenue. Toutes les opérations d'ajout de la vérification de licence et de gestion de la réponse sont effectuées dans votre fichier source activité (Activity) principale.

Pour ajouter la vérification des licences et gérer la réponse, vous devez :

  1. Ajouter des importations
  2. Implémenter LicenseCheckerCallback en tant que classe interne privée
  3. Créer un gestionnaire pour la publication depuis LicenseCheckerCallback vers le thread UI
  4. Instancier LicenseChecker et LicenseCheckerCallback
  5. Appeler checkAccess() pour lancer la vérification des licences
  6. Intégrer votre clé publique pour l'attribution des licences
  7. Appeler la méthode onDestroy() de la fonction LicenseChecker pour fermer les connexions IPC.

Les sections ci-dessous détaillent les étapes précédemment citées.

Présentation de la vérification des licences et des réponses

Dans la plupart des cas, vous devez ajouter la vérification des licences à l'Activity principale de votre application, dans la méthode onCreate(). Ainsi, lorsque l'utilisateur lance directement votre application, la vérification de licence est immédiatement appelée. Dans certains cas, vous pouvez également ajouter des vérifications de licence à d'autres emplacements. Par exemple, si votre application inclut plusieurs composants "Activity" que d'autres applications peuvent démarrer depuis Intent, vous pouvez ajouter des vérifications de licence dans ces activités.

La vérification des licences se compose de deux actions principales :

  • Un appel à une méthode pour lancer la vérification de licence : dans la bibliothèque LVL, il s'agit d'un appel à la méthode checkAccess() d'un objet LicenseChecker que vous créez.
  • Rappel qui renvoie le résultat de la vérification des licences. Dans la bibliothèque LVL, il s'agit d'une interface LicenseCheckerCallback que vous implémentez. L'interface déclare deux méthodes, allow() et dontAllow(), qui sont appelées par la bibliothèque en fonction du résultat de la vérification des licences. Vous implémentez ces deux méthodes avec la logique dont vous avez besoin pour autoriser ou interdire à l'utilisateur l'accès à votre application. Notez que ces méthodes ne déterminent pas s'il faut autoriser l'accès. Cette détermination relève de votre implémentation de Policy. Ces méthodes fournissent simplement les comportements de l'application indiquant comment autoriser et interdire l'accès (et gérer les erreurs d'application).

    Les méthodes allow() et dontAllow() fournissent un "motif" pour leur réponse, qui peut être l'une des valeurs Policy, LICENSED, NOT_LICENSED ou RETRY. Vous devez plus particulièrement gérer le cas où la méthode reçoit la réponse RETRY pour dontAllow() et fournir à l'utilisateur un bouton "Réessayer", ce qui peut être dû au fait que le service était indisponible au moment de la demande.

Figure 1 : Présentation d'une interaction de vérification de licence type.

Le schéma ci-dessus illustre le processus typique de vérification des licences :

  1. Le code présent dans l'activité principale de l'application instancie les objets LicenseCheckerCallback et LicenseChecker. Lors de la création de LicenseChecker, le code transmet Context, une implémentation de Policy à utiliser et la clé publique du compte d'éditeur pour les licences en tant que paramètres.
  2. Le code appelle ensuite la méthode checkAccess() sur l'objet LicenseChecker. L'implémentation de la méthode appelle la Policy pour déterminer s'il existe une réponse de licence valide mise en cache localement,dans SharedPreferences.
    • Si tel est le cas, l'implémentation de checkAccess() appelle allow().
    • Sinon, LicenseChecker lance une requête de vérification de licence au serveur de gestion des licences.

    Remarque : Le serveur de gestion des licences renvoie toujours LICENSED lorsque vous vérifiez la licence d'une version préliminaire.

  3. Lorsqu'une réponse est reçue, LicenseChecker crée un outil LicenseValidator qui valide les données de licence signées et extrait les champs de la réponse, puis les transmet à votre Policy pour une évaluation plus approfondie.
    • Si la licence est valide, Policy met en cache la réponse dans SharedPreferences et en informe le validateur, qui appelle ensuite la méthode allow() sur l'objet LicenseCheckerCallback.
    • Si la licence n'est pas valide, Policy en informe le validateur, qui appelle la méthode dontAllow() sur LicenseCheckerCallback.
  4. En cas d'erreur locale ou de serveur récupérable, par exemple lorsque le réseau n'est pas disponible pour envoyer la requête, LicenseChecker passe une réponse RETRY à la méthode processServerResponse() de votre objet Policy.

    Les méthodes de rappel allow() et dontAllow() reçoivent également un argument reason. Le motif de la méthode allow() est généralement Policy.LICENSED ou Policy.RETRY, et le motif dontAllow() est généralement Policy.NOT_LICENSED ou Policy.RETRY. Ces valeurs de réponse sont utiles pour afficher une réponse appropriée à l'utilisateur, par exemple en fournissant un bouton "Réessayer" lorsque dontAllow() répond par Policy.RETRY, ce qui peut être dû au fait que le service était indisponible.

  5. En cas d'erreur d'application, par exemple lorsque l'application tente de vérifier la licence d'un nom de package non valide, LicenseChecker transmet une réponse d'erreur à la méthode applicationError() de LicenseCheckerCallback.

Notez qu'en plus de lancer la vérification des licences et de gérer le résultat, décrit dans les sections ci-dessous, votre application doit également fournir une implémentation de la règle et, si le Policy stocke des données de réponse (telles que ServerManagedPolicy), elle doit fournir une implémentation d'offuscateur.

Ajouter des importations

Commencez par ouvrir le fichier de classe de l'activité principale de l'application, puis importez LicenseChecker et LicenseCheckerCallback à partir du package LVL.

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;

Si vous utilisez l'implémentation par défaut de Policy fournie avec la bibliothèque LVL, ServerManagedPolicy, importez-la également avec l'AESObfuscator. Si vous utilisez des Policy ou Obfuscator personnalisés, importez-les à la place.

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;

Implémenter LicenseCheckerCallback en tant que classe interne privée

LicenseCheckerCallback est une interface fournie par la bibliothèque LVL pour gérer le résultat d'une vérification de licence. Pour accepter les licences à l'aide de la bibliothèque LVL, vous devez implémenter LicenseCheckerCallback et ses méthodes pour autoriser ou interdire l'accès à l'application.

Le résultat d'une vérification de licence est toujours un appel à l'une des méthodes LicenseCheckerCallback, effectué à partir de la validation de la charge utile de réponse, du code de réponse du serveur lui-même et de toute opération supplémentaire effectuée par votre Policy. Votre application peut implémenter ces méthodes comme vous le souhaitez. En général, il est préférable d'utiliser des méthodes simples, en les limitant à la gestion de l'état de l'interface utilisateur et de l'accès aux applications. Si vous souhaitez ajouter un traitement supplémentaire aux réponses de licence, par exemple en contactant un serveur backend ou en appliquant des contraintes personnalisées, vous pouvez envisager d'intégrer ce code dans votre Policy au lieu de le placer dans les méthodes LicenseCheckerCallback.

Dans la plupart des cas, vous devez déclarer votre implémentation de LicenseCheckerCallback en tant que classe privée dans la classe d'activité principale de votre application.

Implémentez les méthodes allow() et dontAllow() si nécessaire. Pour commencer, vous pouvez utiliser de simples comportements de gestion des résultats dans les méthodes, comme afficher le résultat de la licence dans une boîte de dialogue. Cela vous permet d'exécuter votre application plus rapidement et peut faciliter le débogage. Plus tard, après avoir déterminé les comportements précis que vous souhaitez, vous pouvez ajouter une gestion plus complexe.

Voici quelques suggestions pour gérer les réponses sans licence dans dontAllow() :

  • Afficher une boîte de dialogue "Réessayer" incluant un bouton pour lancer une nouvelle vérification de licence si le motif (reason) fourni est Policy.RETRY.
  • Afficher une boîte de dialogue "Acheter cette application", avec un bouton qui redirige l'utilisateur vers la page d'informations de l'application sur Google Play, à partir de laquelle il peut acheter l'application Pour en savoir plus sur la configuration de ces liens, consultez la page Associer vos produits.
  • Afficher une notification toast indiquant que les fonctionnalités de l'application sont limitées, car elle ne dispose pas de licence

L'exemple ci-dessous montre comment l'application exemple de la bibliothèque LVL implémente LicenseCheckerCallback, avec des méthodes qui affichent le résultat de la vérification de licence dans une boîte de dialogue.

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);
        }
    }
}

De plus, vous devez implémenter la méthode applicationError(), que la bibliothèque LVL appelle pour permettre à votre application de gérer les erreurs qui ne peuvent pas faire l'objet d'une nouvelle tentative. Pour obtenir la liste de ces erreurs, consultez la section Codes de réponse du serveur dans la documentation de référence sur la gestion des licences. Vous pouvez implémenter la méthode comme vous le souhaitez. Dans la plupart des cas, la méthode doit consigner le code d'erreur et appeler dontAllow().

Créer un gestionnaire pour la publication depuis LicenseCheckerCallback vers le thread UI

Lors d'une vérification de licence, la bibliothèque LVL transmet la requête à l'application Google Play, qui gère la communication avec le serveur de gestion des licences. Ce dernier transmet la requête via l'IPC asynchrone (à l'aide de Binder). Par conséquent, le traitement et la communication réseau ne sont pas effectués sur un thread géré par votre application. De même, lorsque l'application Google Play reçoit le résultat, elle appelle une méthode de rappel sur IPC, qui s'exécute à son tour dans un pool de threads IPC dans le processus de votre application.

La classe LicenseChecker gère la communication IPC de votre application avec l'application Google Play, y compris l'appel qui envoie la requête et le rappel qui reçoit la réponse. LicenseChecker suit également les requêtes de licence ouvertes et gère leurs délais avant expiration.

Pour pouvoir gérer correctement les délais avant expiration et traiter les réponses entrantes sans affecter le thread UI de votre application, LicenseChecker génère un thread d'arrière-plan lors de l'instanciation. Dans le thread, il traite les résultats de la vérification des licences, qu'il s'agisse d'une réponse envoyée par le serveur ou d'une erreur d'expiration de délai. À la fin du traitement, la bibliothèque LVL appelle vos méthodes LicenseCheckerCallback à partir du thread d'arrière-plan.

Pour votre application, cela signifie que :

  1. Vos méthodes LicenseCheckerCallback seront invoquées dans de nombreux cas à partir d'un thread d'arrière-plan.
  2. Ces méthodes ne pourront pas mettre à jour l'état ni appeler de traitement dans le thread UI, sauf si vous créez un gestionnaire dans ce dernier et que vos méthodes de rappel y sont publiées.

Si vous souhaitez que vos méthodes LicenseCheckerCallback mettent à jour le thread UI, instanciez un Handler (gestionnaire) dans la méthode onCreate() de l'activité principale, comme indiqué ci-dessous. Dans cet exemple, les méthodes LicenseCheckerCallback de l'application exemple de la bibliothèque LVL (voir ci-dessus) appellent displayResult() pour mettre à jour le thread UI via la méthode post() du gestionnaire.

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();
    }

Ensuite, dans vos méthodes LicenseCheckerCallback, vous pouvez utiliser les méthodes du gestionnaire pour publier des exécutables ou des objets de message. Voici comment l'exemple d'application inclus dans la bibliothèque LVL publie un exécutable dans un gestionnaire dans le thread UI pour afficher l'état de la licence.

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);
            }
        });
    }

Instancier LicenseChecker et LicenseCheckerCallback

Dans la méthode onCreate() de l'activité principale, créez des instances privées de LicenseCheckerCallback et LicenseChecker. Vous devez d'abord instancier LicenseCheckerCallback, car vous devez transmettre une référence à cette instance lorsque vous appelez le constructeur de LicenseChecker.

Lorsque vous instanciez LicenseChecker, vous devez transmettre les paramètres suivants :

  • Le Context de l'application
  • Une référence à l'implémentation de Policy à utiliser pour la vérification des licences. Dans la plupart des cas, vous devez utiliser l'implémentation par défaut de Policy fournie par la bibliothèque LVL, ServerManagedPolicy.
  • La variable String (chaîne) contenant la clé publique de votre compte d'éditeur pour les licences.

Si vous utilisez ServerManagedPolicy, vous n'aurez pas besoin d'accéder directement à la classe pour l'instancier. Vous pouvez le faire directement dans le constructeur LicenseChecker, comme illustré dans l'exemple ci-dessous. Notez que vous devez transmettre une référence à une nouvelle instance d'offuscateur lorsque vous construisez ServerManagedPolicy.

L'exemple ci-dessous montre l'instanciation de LicenseChecker et de LicenseCheckerCallback à partir de la méthode onCreate() d'une classe Activity.

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.
            );
        ...
    }
}

Notez que LicenseChecker n'appelle les méthodes LicenseCheckerCallback à partir du thread UI que si une réponse de licence valide est mise en cache localement. Si la vérification de licence est envoyée au serveur, les rappels proviennent toujours du thread en arrière-plan, même en cas d'erreurs réseau.

Appeler checkAccess() pour lancer la vérification des licences

Dans votre activité principale, ajoutez un appel à la méthode checkAccess() de l'instance LicenseChecker. Dans l'appel, transmettez une référence à votre instance LicenseCheckerCallback en tant que paramètre. Si vous devez gérer des effets spéciaux dans l'interface utilisateur ou des états avant l'appel, il peut être utile d'appeler checkAccess() à partir d'une méthode de type wrapper. Par exemple, l'application exemple de la bibliothèque LVL appelle checkAccess() à partir d'une méthode de type wrapper doCheck() :

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);
    }

Intégrer votre clé publique pour l'attribution des licences

Pour chaque application, le service Google Play génère automatiquement une paire de clés publique/privée RSA de 2 048 bits utilisée pour les licences et la facturation des achats in-app. La paire de clés est associée de manière unique à l'application. Bien qu'elle soit associée à l'application, la paire de clés est différente de la clé que vous utilisez pour signer vos applications (ou dérivée de celle-ci).

La Google Play Console expose la clé publique d'attribution de licence à tout développeur connecté à la Play Console, mais elle la masque à tous les utilisateurs dans un emplacement sécurisé. Lorsqu'une application demande une vérification des licences d'une application publiée dans votre compte, le serveur de gestion des licences signe la réponse de licence à l'aide de la clé privée de la paire de clés de votre application. Lorsque la bibliothèque LVL reçoit la réponse, elle utilise la clé publique fournie par l'application pour vérifier la signature de la réponse de la licence.

Pour ajouter une licence à une application, vous devez obtenir sa clé publique de licence et la copier dans votre application. Voici comment trouver la clé publique d'attribution de licence de votre application :

  1. Accédez à la Google Play Console et connectez-vous. Veillez à vous connecter au compte à partir duquel l'application sous licence est publiée (ou sera publiée).
  2. Sur la page d'informations de l'application, recherchez le lien Services et API, puis cliquez dessus.
  3. Sur la page Services et API, accédez à la section Gestion des licences et facturation des achats in-app. Votre clé publique de licence est indiquée dans le champ Votre clé de licence pour cette application.

Pour ajouter la clé publique à votre application, il vous suffit de copier/coller la chaîne de la clé depuis le champ dans votre application en tant que valeur de la variable de la chaîne BASE64_PUBLIC_KEY. Assurez-vous d'avoir sélectionné toute la chaîne de la clé lorsque vous la copiez, sans omettre de caractères.

Voici un exemple de l'application exemple de la bibliothèque 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
    ...
}

Appeler la méthode onDestroy() de la fonction LicenseChecker pour fermer les connexions IPC.

Enfin, pour laisser la bibliothèque LVL effectuer un nettoyage avant que votre Context d'application ne soit modifié, ajoutez un appel à la méthode onDestroy() de LicenseChecker à partir de l'implémentation onDestroy() de votre activité. L'appel force LicenseChecker à fermer correctement toute connexion IPC ouverte au service ILicensingService de l'application Google Play et supprime toutes les références locales au service et au gestionnaire.

L'échec de l'appel de la méthode onDestroy() de LicenseChecker peut entraîner des problèmes tout au long du cycle de vie de votre application. Par exemple, si l'utilisateur change d'orientation d'écran pendant qu'une vérification de licence est active, l'application Context est détruite. Si votre application ne ferme pas correctement la connexion IPC de LicenseChecker, elle plantera une fois la réponse reçue. De même, si l'utilisateur quitte votre application alors qu'une vérification de licence est en cours, celle-ci plantera une fois la réponse reçue, sauf si elle a correctement appelé la méthode onDestroy() de LicenseChecker pour vous déconnecter du service.

Voici un exemple de l'application exemple incluse dans la bibliothèque LVL, où mChecker est l'instance LicenseChecker :

Kotlin

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

Java

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

Si vous étendez ou modifiez LicenseChecker, vous devrez peut-être également appeler la méthode finishCheck() de LicenseChecker pour nettoyer toutes les connexions IPC ouvertes.

Implémenter un DeviceLimiter

Il se peut que dans certains cas vous souhaitiez que votre Policy limite le nombre d'appareils autorisés à utiliser une seule licence. Cela empêcherait un utilisateur de déplacer une application sous licence vers un certain nombre d'appareils et de l'utiliser sur ces appareils avec le même ID de compte. Cela empêcherait également un utilisateur de "partager" l'application en fournissant les informations de compte associées à la licence à d'autres personnes, qui pourraient alors se connecter à ce compte sur leurs appareils et accéder à la licence pour l'application.

La bibliothèque LVL prend en charge les licences par appareil en fournissant une interface DeviceLimiter, qui déclare une seule méthode : allowDeviceAccess(). Lorsqu'un LicenseValidator traite une réponse du serveur de gestion des licences, il appelle allowDeviceAccess() en transmettant une chaîne d'ID utilisateur extraite de la réponse.

Si vous ne souhaitez pas utiliser la fonctionnalité de limitation des appareils, aucune action n'est requise. La classe LicenseChecker utilise automatiquement une implémentation par défaut appelée NullDeviceLimiter. Comme son nom l'indique, NullDeviceLimiter est une classe "no-op" dont la méthode allowDeviceAccess() renvoie simplement une réponse LICENSED pour tous les utilisateurs et appareils.

Attention : L'attribution de licences par appareil est déconseillée pour la plupart des applications, car :

  • Vous devez fournir un serveur backend pour gérer le mappage des utilisateurs et des appareils, et
  • Un utilisateur pourrait par erreur se voir refuser l'accès à une application qu'il a légitimement achetée sur un autre appareil.

Obscurcir votre code

Pour assurer la sécurité de votre application, en particulier pour une application payante qui utilise des licences et/ou des protections et contraintes personnalisées, obscurcir le code de votre application est primordial. En obscurcissant correctement votre code, il est plus difficile pour un utilisateur malveillant de décompiler le bytecode de l'application, de le modifier (par exemple en supprimant la vérification des licences) puis de le recompiler.

Plusieurs programmes d'obscurcissement sont disponibles pour les applications Android, dont ProGuard, qui propose également des fonctionnalités d'optimisation du code. L'utilisation de ProGuard ou d'un programme similaire pour obscurcir votre code est fortement recommandée pour toutes les applications utilisant le service de gestion des licences Google Play.

Publier une application sous licence

Lorsque vous avez terminé de tester l'implémentation de votre licence, vous êtes prêt à publier l'application sur Google Play. Suivez la procédure normale pour préparer, signer, puis publier l'application.

Où obtenir de l'aide ?

Si vous avez des questions ou rencontrez des difficultés lors de l'implémentation ou du déploiement de la publication dans vos applications, veuillez utiliser les ressources d'assistance répertoriées dans le tableau ci-dessous. En orientant vos requêtes vers le forum approprié, vous pouvez obtenir l'assistance dont vous avez besoin plus rapidement.

Tableau 2. Ressources d'assistance destinées aux développeurs pour le service de gestion des licences de Google Play.

Type d'assistance Ressource Thèmes abordés
Problèmes liés au développement et au test Google Groupes : android-developers Téléchargement et intégration de la bibliothèque LVL, projets de bibliothèque, questions relatives à Policy, idées d'expérience utilisateur, gestion des réponses, Obfuscator, IPC, configuration de l'environnement de test
Stack Overflow : http://stackoverflow.com/questions/tagged/android
Problèmes liés aux comptes, à la publication et au déploiement Forum d'aide de Google Play Comptes d'éditeur, paire de clés de licence, comptes de test, réponses du serveur, réponses de test, déploiement d'applications et résultats
Questions fréquentes à l'assistance concernant licences Market
Outils de suivi des problèmes pour la bibliothèque LVL Outil de suivi des problèmes liés aux projets Marketlicensing Rapports sur les bugs et problèmes spécifiques liés aux classes de code source de la bibliothèque LVL et aux implémentations d'interface

Pour obtenir des informations générales sur la publication de messages dans les groupes cités ci-dessus, consultez la section Ressources de la communauté de la page "Ressources d'assistance pour les développeurs".

Autres ressources

L'application exemple incluse dans la bibliothèque LVL fournit un exemple complet de comment initier une vérification de licence et comment gérer le résultat obtenu dans la classe MainActivity.