Skip to content

SDK-Integration

Java-SDK-Integration

Fügen Sie Keyright-Lizenzierung zu einer Java-App hinzu: die Abhängigkeit com.delta1labs:keyright hinzufügen, Lizenzen offline prüfen, online aktivieren, Funktionen über Entitlements schützen sowie Testversionen und netzwerkisolierte (air-gapped) Maschinen behandeln — konstruktionsbedingt fail-closed.

Diese Seite ist die durchgängige Version für den Java-Client: vom Hinzufügen der Abhängigkeit bis zu einer ausgelieferten JVM-App, die bezahlte Funktionen gegen einen echten Lizenzschlüssel freischaltet. Das Artefakt com.delta1labs:keyright prüft dieselben Lizenz- und Lease-Formate wie die SDKs für .NET, Node.js und Python, sodass eine mehrsprachige Produktlinie einen Keyright-Mandanten und einen öffentlichen Schlüssel teilen kann.

Die Anbietereinrichtung — ein Produkt anlegen, den öffentlichen Schlüssel holen, Tiers und Entitlements definieren, Schlüssel ausstellen — ist sprachunabhängig und wird einmal in Erste Schritte und, in Entwicklertiefe, im Leitfaden .NET-SDK-Integration behandelt. Diese Seite setzt voraus, dass Sie das erledigt haben, und konzentriert sich auf den Java-Client-Code.

Das Modell in einem Absatz

Sie stellen Lizenzschlüssel serverseitig in Keyright aus; jeder Mandant signiert sie mit einem privaten RSA-Schlüssel, der den Server nie verlässt. Ihre App bettet nur den passenden öffentlichen Schlüssel ein und prüft Lizenzen offline dagegen, sodass eine Lizenzprüfung kein Netzwerk braucht. Für Platzdurchsetzung und Widerruf aktiviert die App zusätzlich online: Sie tauscht einen Lizenzschlüssel gegen einen kurzlebigen signierten Lease, der an die Maschine gebunden ist, und arbeitet dann offline weiter, bis dieser Lease abläuft. Alles, was nicht stimmt — ungültige Signatur, falsches Produkt, Ablauf, Widerruf, eine zurückgestellte Uhr — löst zur Edition Free auf, statt eine Ausnahme zu werfen. Es ist fail-closed.

Schritt 1 — Abhängigkeit hinzufügen

Das SDK wird unter com.delta1labs:keyright veröffentlicht. Es zielt auf Java 17+, liegt im Paket com.delta1labs.keyright und hat keine Laufzeitabhängigkeiten (es verwendet nur den JDK-eigenen java.net.http-Client und dessen Kryptografie).

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")

Schritt 2 — Client initialisieren

Erzeugen Sie beim Start einen KeyrightClient mit der statischen Factory initialize. KeyrightOptions stellt Fluent-Setter für die gängigen Felder bereit; übergeben Sie den Produkt-Slug, für den Sie Schlüssel ausstellen, den base64-kodierten öffentlichen Schlüssel vom Tab Integration Ihres Dashboards und — für die Online-Aktivierung — Ihre Service-URL:

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")                                 // muss mit dem Produkt-Slug übereinstimmen, für den Sie Schlüssel ausstellen
            .publicKeyBase64("MIIBIjANBgkq...")                  // base64-kodierte SubjectPublicKeyInfo vom Tab Integration
            .serviceUrl("https://keyright.delta1labs.com"));     // weglassen, wenn Sie nur Offline-Lizenzdateien ausliefern
}

Nur publicKeyBase64 ist zwingend erforderlich — initialize wirft IllegalArgumentException, wenn er fehlt. Der öffentliche Schlüssel ist kein Geheimnis: Er kann Signaturen nur prüfen, nie erzeugen, daher ist es sicher, ihn in Ihrem JAR auszuliefern.

Die Fluent-Setter decken die Felder ab, die Sie normalerweise anfassen: product, publicKeyBase64, serviceUrl, licenseString, licenseFilePath, envVarName (Standard "KEYRIGHT_LICENSE"), configLicensePath, defaultLicensePath, leaseCachePath, revocationListJson, localStatePath, now, machineComponents, clockTamperToleranceHours und transport. Einige Optionen sind einfache öffentliche Felder ohne Fluent-Setter — setzen Sie sie direkt am Optionsobjekt:

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

// einen alten Schlüssel während eines Rotationsfensters gültig halten (öffentliches Feld, kein Setter)
opts.additionalPublicKeysBase64.add("<previous public key>");

// Node-Lock-Toleranz lockern, wenn Sie volatile Hardware fingerprinten (Standard 1)
opts.nodeLockTolerance = 2;

// eine signierte Widerrufsliste aus einer Datei ausliefern (öffentliches Feld, kein Setter)
opts.revocationListPath = "/opt/acme/revocations.json";

KeyrightClient client = KeyrightClient.initialize(opts);

Das SDK sucht eine Lizenz in mehreren Quellen, höchste Rangfolge zuerst: ein explizites licenseString, dann ein explizites licenseFilePath, dann die von der Umgebungsvariable envVarName benannte Datei, dann configLicensePath, dann der App-Daten-Standardpfad des Betriebssystems (<LocalAppData>/Keyright/<product>/license.json) und schließlich der zwischengespeicherte Aktivierungs-Lease (.../lease.json). Normalerweise setzen Sie keinen dieser Pfade — die Aktivierung (Schritt 4) schreibt den Lease-Cache für Sie.

Schritt 3 — Funktionen offline schützen

Rufen Sie validate() auf. Es ermittelt die beste Lizenz aus den obigen Quellen und prüft RSA-Signatur, Produktübereinstimmung, Node-Lock, Ablauf, die optionale mitgelieferte Widerrufsliste sowie den Testversions-/Uhrmanipulationsstatus — alles offline, und es wirft nie eine Ausnahme. Es liefert ein KeyrightClient.Result mit zwei öffentlichen Feldern: info (eine LicenseInfo) und source (welche Quelle gewonnen hat, ein 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 für jede Edition oberhalb von Free
    // bezahlte Funktionen freischalten
}

if (info.edition == Edition.ENTERPRISE) {
    // Enterprise-exklusive Funktionen freischalten
}

// Diagnose: Welche Quelle hat die Lizenz ermittelt?
if (result.source == KeyrightClient.Source.LEASE) {
    // läuft auf einem zwischengespeicherten Aktivierungs-Lease
}

Wenn Sie nur die Lizenz und nicht die Quelle brauchen, liefert validateInfo() die LicenseInfo direkt.

Beachten Sie, dass auf LicenseInfo die Lizenzdaten als öffentliche finale Felder bereitgestellt werden, während alles Berechnete eine Methode ist. Die Felder sind status (ein LicenseStatus), edition (eine Edition), licensee, expiryUtc (ein Instant, nullable), isTrial (ein boolean), trialDays (ein Integer, nullable), product, licenseId, entitlements (ein EntitlementSet) und message. Die abgeleiteten Helfer sind Methoden: isValid() (Status ist VALID), isPaid() (Edition liegt oberhalb von FREE), daysRemaining() (ein Integer oder null für eine unbefristete Lizenz), isExpiringSoon(int withinDays), kind() (TRIAL / SUBSCRIPTION / PERPETUAL) und statusBadge() (z. B. "Enterprise" oder "Enterprise Trial").

Über Entitlements schützen, nicht nur über die Edition

Schützen Sie einzelne Funktionen über Entitlements, damit eine Änderung der Tier-Vorlage nicht bedeutet, neuen Code auszuliefern. EntitlementSet hat zwei Zugriffsmethoden — isEnabled(name) für Flags und getLimit(name, fallback) für numerische Limits — und beide sind fail-closed:

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

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

// Boolesches Flag — ein fehlender oder unbekannter Wert gilt als deaktiviert
if (ent.isEnabled("export")) {
    showExportCommand();
}

// Numerisches Limit — den fail-closed-Fallback geben Sie selbst an.
// "unlimited" wird zu EntitlementSet.MAXLONG (Long.MAX_VALUE) aufgelöst.
long maxProjects = ent.getLimit("max-projects", 1);
if (currentProjectCount >= maxProjects) {
    promptToUpgrade();
}

isEnabled behandelt "true", "1", "yes" und "enabled" (ohne Berücksichtigung der Groß-/Kleinschreibung) als eingeschaltet; alles andere, einschließlich eines fehlenden Namens, ist ausgeschaltet. getLimit parst den Wert als long, bildet das Literal "unlimited" auf EntitlementSet.MAXLONG ab und liefert Ihren fallback, wenn der Name fehlt oder nicht parsbar ist. Kombinieren Sie eine Entitlement-Prüfung mit der Edition, wenn eine Funktion sowohl geschützt als auch tier-spezifisch ist — und lesen Sie eine validierte LicenseInfo einmal und verwenden Sie sie dann wieder, statt pro Prüfung erneut zu validieren:

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

Der Client stellt außerdem Einzeiler-Komfortmethoden bereit — client.isEnabled("export"), client.getLimit("max-projects", 1), client.entitlements() und client.edition() —, aber jede davon validiert an Ort und Stelle; bevorzugen Sie daher ein einzelnes validateInfo(), wenn Sie mehrere Dinge zusammen prüfen.

Um herauszufinden, warum eine Prüfung fehlgeschlagen ist (für einen „Registrieren“-Dialog oder Diagnosen), lesen Sie info.status und die für Menschen gedachte info.message.

Schritt 4 — Online mit einem Lizenzschlüssel aktivieren

Wenn ein Kunde einen Schlüssel eingibt, rufen Sie activate(key) auf. Es ist synchron und sendet den Schlüssel plus eine stabile Maschinen-ID an Ihren Service, der einen Platz belegt und einen kurzlebigen, an diese Maschine gebundenen signierten Lease zurückgibt. Das SDK prüft den Lease gegen Ihren eingebetteten öffentlichen Schlüssel und speichert ihn lokal zwischen, sodass jedes spätere validate() ohne Netzwerk erfolgreich ist, bis die Kulanzfrist des Lease abläuft.

activate wirft für die gewöhnlichen Fehlerpfade (ungültiger Schlüssel, Platzlimit, offline, widerrufen) keine Ausnahme — es liefert wie validate() eine fail-closed LicenseInfo. Prüfen Sie das Ergebnis:

LicenseInfo info = client.activate(customerEnteredKey);

if (info.isValid() && info.isPaid()) {
    // Aktiviert. Der Lease ist zwischengespeichert; die App arbeitet jetzt offline, bis er abläuft.
    showLicensedUi(info.statusBadge());        // z. B. "Enterprise" oder "Enterprise Trial"
} else {
    // info.message anzeigen; die App bleibt im Modus Free.
    showActivationError(info.message);         // "All seats for this license are in use." usw.
}
  • Plätze werden serverseitig durchgesetzt. Das Aktivieren von mehr Maschinen, als die Lizenz erlaubt, liefert ein Platzlimit-Ergebnis (Status NO_LICENSE, Meldung „All seats for this license are in use.“) und keinen Lease. Das erneute Aktivieren einer bereits gebundenen Maschine ist idempotent — es wird kein zusätzlicher Platz belegt.
  • Offline-Kulanz. Ist der Service nicht erreichbar, greift activate auf einen noch gültigen zwischengespeicherten Lease zurück, sodass ein kurzer Ausfall den Benutzer nicht aussperrt. Nur wenn kein gültiger zwischengespeicherter Lease vorhanden ist, liefert es ein fail-closed Ergebnis „Could not reach issuing service“.
  • Widerruf wird bei der nächsten Aktualisierung wirksam — der Client fällt mit dem Status REVOKED auf Free zurück.

activate wirft nur bei Programmierfehlern eine Ausnahme: IllegalStateException, wenn keine serviceUrl konfiguriert wurde, oder IllegalArgumentException bei einem leeren Schlüssel.

Die gesendete Maschinen-ID ist dieselbe, die Sie für den Support anzeigen können:

import com.delta1labs.keyright.MachineFingerprint;

String machineId = MachineFingerprint.current().toBoundString();   // z. B. "KRM1:...:...:..."

Schritt 5 — Deaktivieren (Platz freigeben)

Um eine Lizenz auf eine andere Maschine zu übertragen, geben Sie zuerst den Platz dieser Maschine frei. deactivate(key) sendet an Ihren Service und liefert dessen Status-String ("ok" oder "not_found"). Bei "ok" löscht es den lokal zwischengespeicherten Lease, sodass nachfolgende Offline-Prüfungen korrekt auf Free zurückfallen.

Anders als die Aktivierung hat die Deaktivierung keinen Offline-Fallback — das Freigeben eines Platzes ist eine serverseitige Änderung; ist der Service also nicht erreichbar (oder antwortet er mit Nicht-2xx oder unlesbar), wirft sie 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 nicht erreichbar oder Fehler zurückgegeben — der Platz wurde NICHT freigegeben
    showError("Couldn't reach the licensing service. Try again while online.");
}

Schritt 6 — Netzwerkisolierte (air-gapped) Maschinen (Offline-Aktivierung)

Für eine Maschine, die den Service nie erreichen kann, signiert ein Operator einen Offline-Lease für deren Maschinen-ID (Dashboard Licenses → offline lease oder POST /admin/licenses/{id}/offline-lease) und liefert das JSON per Datei aus. Ermitteln Sie die ID der Zielmaschine mit MachineFingerprint.current().toBoundString(), übergeben Sie sie dem Operator und importieren Sie dann den zurückgelieferten Lease.

importOfflineLease(json) prüft den Lease gegen Ihre(n) öffentlichen Schlüssel, Produkt, Maschine und Ablauf — derselbe Pfad, den validate() verwendet — und speichert ihn lokal zwischen, sodass spätere Prüfungen ohne Netzwerk erfolgreich sind. Es wirft eine Ausnahme, wenn der Lease ungültig ist: IllegalArgumentException bei fehlender oder Nicht-JSON-Eingabe, IllegalStateException, wenn Signatur, Produkt, Maschine oder Ablauf nicht stimmen.

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) {
    // falsche Signatur / falsches Produkt / falsche Maschine oder abgelaufen
    showError("That offline lease isn't valid for this machine: " + ex.getMessage());
}

Schritt 7 — Node-Locking, Testversionen & Widerruf

  • Node-Locking. Das SDK leitet einen stabilen Maschinen-Fingerabdruck aus der Maschinen-GUID des Betriebssystems, dem Hostnamen und dem Betriebssystem-Deskriptor ab, byte-kompatibel mit den anderen SDKs. Eine kleine nodeLockTolerance (Standard 1) sorgt dafür, dass eine getauschte Netzwerkkarte oder Festplatte den Benutzer nicht aussperrt. Die Aktivierungsanfrage sendet MachineFingerprint.current().toBoundString(), sodass Plätze pro Gerät gezählt werden; MachineFingerprint.current().displayId() liefert eine kurze, für Menschen lesbare KR-…-ID für den Support. Um zu steuern, was eine Maschine identifiziert (zum Beispiel in einem Container oder in einem Test), setzen Sie machineComponents in den Optionen auf eine Liste von {key, value}-String-Paaren.
  • Testversionen. Ein Testschlüssel wird über exakt denselben activate-Pfad aktiviert wie ein bezahlter. Zeigen Sie den Zustand direkt aus LicenseInfo an — info.isTrial, info.statusBadge() (z. B. „Enterprise Trial“), info.expiryUtc und info.daysRemaining() für ein Badge „noch 27 Tage“. Eine Testversion mit einer trialDays-Anzahl und ohne festes Ablaufdatum wird an der ersten Aktivierung auf dieser Maschine verankert und lokal heruntergezählt; sie wird nahtlos abgelöst, wenn der Kunde später einen bezahlten Schlüssel aktiviert. Vollständiger Ablauf: Kostenlose Self-Service-Testversionen.
  • Widerruf. Widerrufen Sie einen Schlüssel über das Dashboard (oder POST /admin/licenses/{id}/revoke); der Client fällt bei der nächsten Lease-Aktualisierung auf Free zurück (Status REVOKED). Für eine rein offline arbeitende App liefern Sie eine signierte Widerrufsliste mit Ihrem Build über revocationListJson (Fluent-Setter) oder revocationListPath (öffentliches Feld) aus, damit sie Widerrufe weiterhin berücksichtigt.
  • Schlüsselrotation. Wenn Sie den Mandanten-Signaturschlüssel rotieren, liefern Sie einen Build mit dem neuen öffentlichen Schlüssel in publicKeyBase64 und dem auslaufenden in additionalPublicKeysBase64 aus — Lizenzen und Leases, die mit einem der beiden signiert wurden, bleiben während des Übergangs gültig. Entfernen Sie den alten Schlüssel, sobald jeder damit signierte Lease abgelaufen ist.

Fehlerbehebung

  • Die Aktivierung meldet, der Schlüssel sei nicht erkannt worden. Die häufigste Ursache ist eine Produktabweichung: Der Schlüssel wurde für ein anderes Produkt (oder einen anderen Mandanten) ausgestellt als der product-Slug, mit dem Ihr Client konfiguriert ist. Stellen Sie sicher, dass der Slug in Schritt 2 mit dem Produkt übereinstimmt, unter dem Sie den Schlüssel ausgestellt haben.
  • Alles wird als Free erkannt. Das ist Absicht — das SDK ist bei jedem Prüfproblem fail-closed, statt eine Ausnahme zu werfen. Lesen Sie info.status (NO_LICENSE, MALFORMED, SIGNATURE_INVALID, EXPIRED, MACHINE_MISMATCH, REVOKED, CLOCK_TAMPERED) und info.message, um zu sehen, welches Problem vorliegt. Ein SIGNATURE_INVALID bedeutet fast immer, dass der eingebettete öffentliche Schlüssel nicht zu dem Mandanten passt, der den Schlüssel signiert hat.
  • Eine zeitlich begrenzte Lizenz lässt sich plötzlich nicht mehr validieren. Wird die Systemuhr bei einer Testversion oder einem Abonnement um mehr als clockTamperToleranceHours (Standard 24 h) zurückgestellt, liefert das SDK den Status CLOCK_TAMPERED. Das bleibt nicht haften — stellen Sie die korrekte Zeit ein, und die Validierung erholt sich beim nächsten Aufruf. Unbefristete Lizenzen unterliegen dieser Prüfung nie.
  • IllegalStateException aus activate oder deactivate. Diese werfen nur bei Konfigurations-/Verbindungsproblemen eine Ausnahme: keine serviceUrl gesetzt oder (bei deactivate) der Service nicht erreichbar. Gewöhnliche Lizenzierungsergebnisse werfen nie eine Ausnahme — prüfen Sie stattdessen die zurückgegebene LicenseInfo bzw. den Status-String.

Siehe auch