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
activateauf 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
REVOKEDaufFreezurü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(Standard1) sorgt dafür, dass eine getauschte Netzwerkkarte oder Festplatte den Benutzer nicht aussperrt. Die Aktivierungsanfrage sendetMachineFingerprint.current().toBoundString(), sodass Plätze pro Gerät gezählt werden;MachineFingerprint.current().displayId()liefert eine kurze, für Menschen lesbareKR-…-ID für den Support. Um zu steuern, was eine Maschine identifiziert (zum Beispiel in einem Container oder in einem Test), setzen SiemachineComponentsin 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 ausLicenseInfoan —info.isTrial,info.statusBadge()(z. B. „Enterprise Trial“),info.expiryUtcundinfo.daysRemaining()für ein Badge „noch 27 Tage“. Eine Testversion mit einertrialDays-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 aufFreezurück (StatusREVOKED). Für eine rein offline arbeitende App liefern Sie eine signierte Widerrufsliste mit Ihrem Build überrevocationListJson(Fluent-Setter) oderrevocationListPath(ö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
publicKeyBase64und dem auslaufenden inadditionalPublicKeysBase64aus — 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) undinfo.message, um zu sehen, welches Problem vorliegt. EinSIGNATURE_INVALIDbedeutet 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 StatusCLOCK_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. IllegalStateExceptionausactivateoderdeactivate. Diese werfen nur bei Konfigurations-/Verbindungsproblemen eine Ausnahme: keineserviceUrlgesetzt oder (beideactivate) der Service nicht erreichbar. Gewöhnliche Lizenzierungsergebnisse werfen nie eine Ausnahme — prüfen Sie stattdessen die zurückgegebeneLicenseInfobzw. den Status-String.
Siehe auch
- Erste Schritte — der komprimierte Anbieterpfad und die Go-live-Checkliste.
- .NET-SDK-Integration — derselbe Weg mit der Anbietereinrichtung in voller Entwicklertiefe.
- Aktivierungs- & Lease-Lebenszyklus — wie Aktivierung, Leases, Offline-Kulanz und Aktualisierung zusammenspielen.
- Sicherheit & Leases — das Signaturmodell, was ein Geheimnis ist und was nicht, und die Lease-Prüfung.
- HTTP-API-Referenz — die vollständige Endpunktübersicht hinter diesen Aufrufen.
- Samples-Repository — lauffähige Client-Beispiele.