Skip to content

Intégration du SDK

Intégration du SDK Java

Ajoutez les licences Keyright à une application Java : ajoutez la dépendance com.delta1labs:keyright, vérifiez les licences hors ligne, activez en ligne, verrouillez les fonctionnalités sur les droits, et gérez les essais et les machines isolées (air-gapped) — en échouant en mode fermé par conception.

Cette page est la version de bout en bout du client Java : de l’ajout de la dépendance à une application JVM livrée qui débloque des fonctionnalités payantes contre une vraie clé de licence. L’artefact com.delta1labs:keyright vérifie les mêmes formats de licence et de lease que les SDK .NET, Node.js et Python, de sorte qu’une gamme de produits multi-langages peut partager un seul tenant Keyright et une seule clé publique.

La configuration éditeur — créer un produit, obtenir votre clé publique, définir les niveaux et les droits, émettre des clés — est indépendante du langage et est couverte une fois dans Premiers pas et, en profondeur pour le développeur, dans le guide Intégration du SDK .NET. Cette page suppose que vous l’avez faite et se concentre sur le code client Java.

Le modèle en un paragraphe

Vous émettez des clés de licence côté serveur dans Keyright ; chaque tenant les signe avec une clé privée RSA qui ne quitte jamais le serveur. Votre application n’embarque que la clé publique correspondante et vérifie les licences hors ligne avec celle-ci ; une vérification de licence n’a donc besoin d’aucun réseau. Pour l’application des sièges et la révocation, l’application active aussi en ligne : elle échange une clé de licence contre une lease signée de courte durée, liée à la machine, puis continue de fonctionner hors ligne jusqu’à l’expiration de cette lease. Tout ce qui ne passe pas la vérification — mauvaise signature, mauvais produit, expiration, révocation, horloge reculée — se résout en édition Free plutôt que de lever une exception. Le SDK échoue en mode fermé (fail closed).

Étape 1 — Ajoutez la dépendance

Le SDK est publié sous com.delta1labs:keyright. Il cible Java 17+, réside dans le package com.delta1labs.keyright et n’a aucune dépendance à l’exécution (il n’utilise que le client java.net.http et la cryptographie du JDK lui-même).

Maven :

<dependency>
  <groupId>com.delta1labs</groupId>
  <artifactId>keyright</artifactId>
  <version>1.1.4</version>
</dependency>

Gradle (Kotlin DSL) :

implementation("com.delta1labs:keyright:1.1.4")

Étape 2 — Initialisez le client

Construisez un seul KeyrightClient au démarrage avec la fabrique statique initialize. KeyrightOptions expose des setters fluents pour les champs courants ; passez le slug du produit pour lequel vous émettez des clés, la clé publique en base64 de l’onglet Integration de votre tableau de bord et — pour l’activation en ligne — l’URL de votre service :

import com.delta1labs.keyright.KeyrightClient;
import com.delta1labs.keyright.KeyrightOptions;

public final class Licensing {
    public static final KeyrightClient CLIENT = KeyrightClient.initialize(
        new KeyrightOptions()
            .product("acme-app")                                 // doit correspondre au slug du produit pour lequel vous émettez des clés
            .publicKeyBase64("MIIBIjANBgkq...")                  // SubjectPublicKeyInfo en base64 depuis l'onglet Integration
            .serviceUrl("https://keyright.delta1labs.com"));     // omettez-le si vous ne livrez que des fichiers de licence hors ligne
}

Seul publicKeyBase64 est strictement obligatoire — initialize lève IllegalArgumentException s’il manque. La clé publique n’est pas un secret : elle ne peut que vérifier des signatures, jamais en produire, de sorte que la livrer dans votre JAR est sans risque.

Les setters fluents couvrent les champs que vous manipulez habituellement : product, publicKeyBase64, serviceUrl, licenseString, licenseFilePath, envVarName (par défaut "KEYRIGHT_LICENSE"), configLicensePath, defaultLicensePath, leaseCachePath, revocationListJson, localStatePath, now, machineComponents, clockTamperToleranceHours et transport. Quelques options sont de simples champs publics sans setter fluent — définissez-les directement sur l’objet d’options :

KeyrightOptions opts = new KeyrightOptions()
    .product("acme-app")
    .publicKeyBase64("MIIBIjANBgkq...")
    .serviceUrl("https://keyright.delta1labs.com");

// garder une ancienne clé valide pendant une fenêtre de rotation (champ public, pas de setter)
opts.additionalPublicKeysBase64.add("<previous public key>");

// assouplir la tolérance de liaison au nœud si vous prenez l'empreinte de matériel volatil (1 par défaut)
opts.nodeLockTolerance = 2;

// livrer une liste de révocation signée lue depuis un fichier (champ public, pas de setter)
opts.revocationListPath = "/opt/acme/revocations.json";

KeyrightClient client = KeyrightClient.initialize(opts);

Le SDK résout une licence à partir de plusieurs sources, par ordre de priorité décroissante : un licenseString explicite, puis un licenseFilePath explicite, puis le fichier nommé par la variable d’environnement envVarName, puis configLicensePath, puis le chemin app-data par défaut du système (<LocalAppData>/Keyright/<product>/license.json), et enfin la lease d’activation en cache (.../lease.json). En général, vous ne définissez aucun de ces chemins — l’activation (étape 4) écrit le cache de lease pour vous.

Étape 3 — Verrouillez les fonctionnalités hors ligne

Appelez validate(). Il résout la meilleure licence parmi les sources ci-dessus, vérifie la signature RSA, la correspondance du produit, la liaison au nœud, l’expiration, la liste de révocation livrée facultative et l’état d’essai/de manipulation de l’horloge — le tout hors ligne, et il ne lève jamais d’exception. Il renvoie un KeyrightClient.Result avec deux champs publics : info (une LicenseInfo) et source (la source qui l’a emporté, un KeyrightClient.Source) :

import com.delta1labs.keyright.KeyrightClient;
import com.delta1labs.keyright.Edition;
import com.delta1labs.keyright.LicenseInfo;

KeyrightClient.Result result = client.validate();
LicenseInfo info = result.info;

if (info.isPaid()) {                        // true pour toute édition au-dessus de Free
    // débloquer les fonctionnalités payantes
}

if (info.edition == Edition.ENTERPRISE) {
    // débloquer les fonctionnalités réservées à Enterprise
}

// diagnostics : quelle source a résolu la licence ?
if (result.source == KeyrightClient.Source.LEASE) {
    // fonctionne sur une lease d'activation en cache
}

Quand vous n’avez besoin que de la licence et pas de la source, validateInfo() renvoie la LicenseInfo directement.

Notez que sur LicenseInfo, les données de licence sont exposées comme des champs public final, tandis que tout ce qui est calculé est une méthode. Les champs sont status (un LicenseStatus), edition (une Edition), licensee, expiryUtc (un Instant, nullable), isTrial (un boolean), trialDays (un Integer, nullable), product, licenseId, entitlements (un EntitlementSet) et message. Les aides dérivées sont des méthodes : isValid() (le statut est VALID), isPaid() (l’édition est au-dessus de FREE), daysRemaining() (un Integer, ou null pour une licence perpétuelle), isExpiringSoon(int withinDays), kind() (TRIAL / SUBSCRIPTION / PERPETUAL) et statusBadge() (p. ex. "Enterprise" ou "Enterprise Trial").

Verrouillez sur les droits, pas seulement sur l’édition

Verrouillez les fonctionnalités individuelles sur les droits pour que modifier le modèle d’un niveau n’oblige pas à livrer du nouveau code. EntitlementSet a deux accesseurs — isEnabled(name) pour les indicateurs et getLimit(name, fallback) pour les limites numériques — et les deux échouent en mode fermé :

import com.delta1labs.keyright.EntitlementSet;
import com.delta1labs.keyright.LicenseInfo;

LicenseInfo info = client.validateInfo();
EntitlementSet ent = info.entitlements;

// Indicateur booléen — une valeur absente ou non reconnue se lit comme désactivée
if (ent.isEnabled("export")) {
    showExportCommand();
}

// Limite numérique — passez vous-même la valeur de repli en mode fermé.
// "unlimited" se résout en EntitlementSet.MAXLONG (Long.MAX_VALUE).
long maxProjects = ent.getLimit("max-projects", 1);
if (currentProjectCount >= maxProjects) {
    promptToUpgrade();
}

isEnabled considère "true", "1", "yes" et "enabled" (sans distinction de casse) comme activé ; tout le reste, y compris un nom absent, est désactivé. getLimit analyse la valeur comme un long, fait correspondre le littéral "unlimited" à EntitlementSet.MAXLONG, et renvoie votre fallback quand le nom est absent ou non analysable. Combinez une vérification de droit avec l’édition quand une fonctionnalité est à la fois verrouillée et propre à un niveau — et lisez une LicenseInfo validée une seule fois, puis réutilisez-la, plutôt que de revalider à chaque vérification :

LicenseInfo info = client.validateInfo();
boolean advancedExport =
       info.edition == Edition.ENTERPRISE
    && info.entitlements.isEnabled("advanced-export");

Le client expose aussi des méthodes pratiques en une ligne — client.isEnabled("export"), client.getLimit("max-projects", 1), client.entitlements() et client.edition() — mais chacune valide sur le moment ; préférez donc un seul validateInfo() quand vous vérifiez plusieurs choses à la fois.

Pour savoir pourquoi une vérification a échoué (pour une boîte de dialogue « Enregistrer » ou des diagnostics), lisez info.status et le info.message destiné aux humains.

Étape 4 — Activez en ligne avec une clé de licence

Quand un client saisit une clé, appelez activate(key). Il est synchrone et envoie la clé plus un identifiant de machine stable à votre service, qui consomme un siège et renvoie une lease signée de courte durée liée à cette machine. Le SDK vérifie la lease avec votre clé publique embarquée et la met en cache localement, de sorte que chaque validate() ultérieur réussit sans réseau jusqu’à l’expiration de la fenêtre de grâce de la lease.

activate ne lève pas d’exception pour les chemins d’échec ordinaires (mauvaise clé, limite de sièges, hors ligne, révoquée) — comme validate(), il renvoie une LicenseInfo en mode fermé. Inspectez le résultat :

LicenseInfo info = client.activate(customerEnteredKey);

if (info.isValid() && info.isPaid()) {
    // Activée. La lease est en cache ; l'application fonctionne désormais hors ligne jusqu'à son expiration.
    showLicensedUi(info.statusBadge());        // p. ex. "Enterprise" ou "Enterprise Trial"
} else {
    // Affichez info.message ; l'application reste en mode Free.
    showActivationError(info.message);         // "All seats for this license are in use.", etc.
}
  • Les sièges sont appliqués côté serveur. Activer plus de machines que la licence ne l’autorise renvoie un résultat de limite de sièges (statut NO_LICENSE, message « All seats for this license are in use. ») et aucune lease. Réactiver une machine déjà liée est idempotent — aucun siège supplémentaire n’est consommé.
  • Grâce hors ligne. Si le service est injoignable, activate se replie sur toute lease en cache encore valide, de sorte qu’une brève panne ne bloque pas l’utilisateur. Ce n’est que lorsqu’il n’y a aucune lease valide en cache qu’il renvoie un résultat en mode fermé « Could not reach issuing service ».
  • La révocation prend effet au prochain rafraîchissement — le client retombe en Free avec le statut REVOKED.

activate ne lève une exception que pour des erreurs de programmation : IllegalStateException si aucun serviceUrl n’a été configuré, ou IllegalArgumentException pour une clé vide.

L’identifiant de machine qu’il envoie est le même que celui que vous pouvez afficher pour le support :

import com.delta1labs.keyright.MachineFingerprint;

String machineId = MachineFingerprint.current().toBoundString();   // p. ex. "KRM1:...:...:..."

Étape 5 — Désactivez (libérez un siège)

Pour déplacer une licence vers une autre machine, libérez d’abord le siège de cette machine. deactivate(key) envoie la requête à votre service et renvoie la chaîne de statut du service ("ok" ou "not_found"). Sur "ok", il supprime la lease en cache local, de sorte que les vérifications hors ligne suivantes retombent correctement en Free.

Contrairement à l’activation, la désactivation n’a aucun repli hors ligne — libérer un siège est une modification côté serveur ; si le service ne peut pas être joint (ou répond en non-2xx ou de façon illisible), elle lève IllegalStateException :

try {
    String status = client.deactivate(customerEnteredKey);
    if ("ok".equals(status)) {
        showMessage("This machine's seat has been released.");
    } else {
        showMessage("No active seat was found for this key on this machine.");
    }
} catch (IllegalStateException ex) {
    // service injoignable ou erreur renvoyée — le siège n'a PAS été libéré
    showError("Couldn't reach the licensing service. Try again while online.");
}

Étape 6 — Machines isolées (activation hors ligne)

Pour une machine qui ne pourra jamais joindre le service, un opérateur signe une lease hors ligne pour son identifiant de machine (Licenses → offline lease dans le tableau de bord, ou POST /admin/licenses/{id}/offline-lease) et livre le JSON par fichier. Obtenez l’identifiant de la machine cible avec MachineFingerprint.current().toBoundString(), remettez-le à l’opérateur, puis importez la lease renvoyée.

importOfflineLease(json) vérifie la lease avec votre ou vos clés publiques, le produit, la machine et l’expiration — le même chemin que celui utilisé par validate() — et la met en cache localement pour que les vérifications ultérieures réussissent sans réseau. Il lève une exception si la lease est invalide : IllegalArgumentException pour une entrée manquante ou non JSON, IllegalStateException si la signature, le produit, la machine ou l’expiration ne passent pas la vérification.

import java.nio.file.Files;
import java.nio.file.Path;

try {
    String leaseJson = Files.readString(Path.of("acme.lease.json"));
    LicenseInfo info = client.importOfflineLease(leaseJson);
    showLicensedUi(info.statusBadge());
} catch (IllegalArgumentException | IllegalStateException ex) {
    // mauvaise signature / produit / machine, ou expirée
    showError("That offline lease isn't valid for this machine: " + ex.getMessage());
}

Étape 7 — Liaison au nœud, essais et révocation

  • Liaison au nœud. Le SDK dérive une empreinte de machine stable à partir du GUID machine de l’OS, du nom d’hôte et du descripteur d’OS, compatible octet pour octet avec les autres SDK. Une petite nodeLockTolerance (par défaut 1) fait qu’une carte réseau ou un disque remplacé ne bloque pas l’utilisateur. La requête d’activation envoie MachineFingerprint.current().toBoundString() pour que les sièges soient comptés par appareil ; MachineFingerprint.current().displayId() donne un identifiant court et lisible KR-… pour le support. Pour contrôler ce qui identifie une machine (par exemple dans un conteneur ou un test), définissez machineComponents dans les options sur une liste de paires de chaînes {key, value}.
  • Essais. Une clé d’essai s’active par exactement le même chemin activate qu’une clé payante. Affichez l’état directement depuis LicenseInfo — info.isTrial, info.statusBadge() (p. ex. « Enterprise Trial »), info.expiryUtc et info.daysRemaining() pour un badge « 27 jours restants ». Un essai avec un compte trialDays et sans expiration fixe est ancré à la première activation sur cette machine et décompté localement ; il est remplacé en douceur quand le client active plus tard une clé payante. Flux complet : Essais gratuits en libre-service.
  • Révocation. Révoquez une clé depuis le tableau de bord (ou POST /admin/licenses/{id}/revoke) ; le client retombe en Free (statut REVOKED) au prochain rafraîchissement de lease. Pour une application purement hors ligne, livrez une liste de révocation signée avec votre build via revocationListJson (setter fluent) ou revocationListPath (champ public) pour qu’elle honore quand même les révocations.
  • Rotation des clés. Quand vous faites tourner la clé de signature du tenant, livrez un build avec la nouvelle clé publique dans publicKeyBase64 et la clé sortante ajoutée à additionalPublicKeysBase64 — les licences et leases signées par l’une ou l’autre continuent de se valider pendant la transition. Retirez l’ancienne clé une fois que chaque lease signée par elle a expiré.

Dépannage

  • L’activation dit que la clé n’a pas été reconnue. La cause la plus fréquente est une non-correspondance de produit : la clé a été émise pour un produit (ou un tenant) différent du slug product avec lequel votre client est configuré. Confirmez que le slug de l’étape 2 correspond au produit sous lequel vous avez émis la clé.
  • Tout se lit comme Free. C’est voulu — le SDK échoue en mode fermé sur tout problème de vérification plutôt que de lever une exception. Lisez info.status (NO_LICENSE, MALFORMED, SIGNATURE_INVALID, EXPIRED, MACHINE_MISMATCH, REVOKED, CLOCK_TAMPERED) et info.message pour savoir lequel. Un SIGNATURE_INVALID signifie presque toujours que la clé publique embarquée ne correspond pas au tenant qui a signé la clé.
  • Une licence à durée limitée refuse soudain de se valider. Si l’horloge système est reculée en arrière au-delà de clockTamperToleranceHours (24 h par défaut) sur un essai ou un abonnement, le SDK renvoie le statut CLOCK_TAMPERED. Ce n’est pas définitif — remettez l’heure correcte et la validation se rétablit à l’appel suivant. Les licences perpétuelles ne sont jamais soumises à cette vérification.
  • IllegalStateException depuis activate ou deactivate. Ils ne lèvent une exception que pour des problèmes de configuration/de connectivité : aucun serviceUrl défini, ou (pour deactivate) un service injoignable. Les résultats de licence ordinaires ne lèvent jamais d’exception — inspectez plutôt la LicenseInfo/la chaîne de statut renvoyée.

Voir aussi