Named-User-Lizenzierung: Rechte an eine Person binden, nicht an eine Maschine
Die Maschinenbindung beantwortet die Frage „Welcher Rechner ist das?" – doch viele Produkte werden pro *Platz* verkauft, nicht pro Kiste. Ein Entwickler mit Laptop, Desktop und CI-Runner ist ein zahlender Nutzer, nicht drei Aktivierungen. Die Named-User-Lizenzierung dreht den Anker um: Die Lizenz gehört einer Identität, und Geräte hängen als Blätter unter einem von Ihnen kontrollierten Limit an ihr. Hier erfahren Sie, wie Sie in Keyright einen signierten Identitätsanspruch modellieren, eine Anmeldung gegen ein kurzlebiges, gerätegebundenes Lease eintauschen, ein Gerätelimit durchsetzen, ohne den Nutzer an eine Kiste zu fesseln, und einem Nutzer erlauben, ein Gerät aus einem vollen Platz zu entfernen – mit dem echten C# zum Ausstellen, Binden und Verifizieren.
Die meisten Lizenzierungsratschläge gehen von der Maschine aus: Nimm den Geräte-Fingerabdruck, binde die Lizenz daran, und nun entspricht eine Aktivierung einer Kiste. Dieses Modell ist sauber und der richtige Standard für viel Desktop-Software. Doch es unterstellt stillschweigend etwas oft Falsches: dass ein Kunde eine Maschine ist. Verkaufen Sie ein Entwicklerwerkzeug, und Ihr Kunde ist eine Person mit einem Laptop, einem Desktop im Büro und einem CI-Agenten, der dasselbe Werkzeug in einem Container ausführt. Unter Node-Locking verbrennt dieser eine zahlende Entwickler drei Aktivierungen und öffnet in der ersten Woche ein Support-Ticket. Die Einheit, die Sie tatsächlich verkauft haben, war ein Platz, und ein Platz gehört einer Person.
Die Named-User-Lizenzierung macht das explizit. Die Lizenz ist an eine Identität verankert – eine E-Mail, eine stabile Subjekt-ID – und Geräte hängen als gedeckelte, widerrufbare Kinder an der Identität. Der Entwickler meldet sich auf drei Maschinen an; der Server übergibt jeder ein kurzlebiges, gerätegebundenes Lease; der Platz bleibt bei eins. Dieser Artikel zeigt, wie man das in Keyright modelliert: wie der signierte Identitätsanspruch aussieht, wie eine Anmeldung zu einem Geräte-Lease wird, wie das Gerätelimit durchgesetzt wird, ohne jemanden an eine Kiste zu fesseln, und wie ein Nutzer ein veraltetes Gerät aus einem vollen Platz entfernt.
Zwei Anker, nicht einer
Die Denkverschiebung besteht darin, nicht mehr „die Lizenz” als ein einziges, an Hardware gebundenes signiertes Blob zu denken, sondern an zwei signierte Artefakte mit unterschiedlichen Lebensdauern. Der Rechtsanspruch ist an die Nutzeridentität gebunden und lebt, solange das Abonnement läuft. Das Geräte-Lease ist an ein Gerät unter dieser Identität gebunden, ist kurzlebig und das, was die laufende Anwendung bei jedem Start tatsächlich verifiziert.
Der Rechtsanspruch ist das, was Sie verkaufen und erneuern. Das Geräte-Lease ist ein abgeleiteter, verwerfbarer Berechtigungsnachweis. Weil das Lease kurzlebig und mit der Geräte-ID darin signiert ist, erhalten Sie den Widerruf fast geschenkt: Stellen Sie keine Leases mehr für ein Gerät aus, und es hört bei der nächsten Erneuerung auf zu funktionieren, ohne dass Sie je in den laufenden Client eingreifen.
Der signierte Identitätsanspruch
Keyright signiert eine kanonische Nutzlast; bei der Named-User-Lizenzierung ist der Anker der Nutzlast das Subjekt (sub) statt eines Maschinen-Fingerabdrucks. Eine minimale Rechtsanspruch-Nutzlast sieht so aus:
public sealed record Entitlement
{
public required string Sub { get; init; } // stable user id, e.g. "usr_7Yq..." (not the raw email)
public required string Product { get; init; } // "acme-tool"
public required string Tier { get; init; } // "Pro"
public required int DeviceCap { get; init; } // seats-per-user: 3
public required DateTimeOffset NotAfter { get; init; } // subscription end
public IReadOnlyList<string> Features { get; init; } = Array.Empty<string>();
}
Zwei bewusste Entscheidungen. Erstens ist Sub eine undurchsichtige, stabile ID – nicht die rohe E-Mail. E-Mails ändern sich; eine Subjekt-ID nicht, und die E-Mail aus dem signierten Claim herauszuhalten bedeutet, dass Sie nicht bei jeder Adressänderung Rechtsansprüche neu ausstellen. Zweitens lebt DeviceCap im signierten Rechtsanspruch, sodass das Limit mit der Lizenz reist und nicht clientseitig bearbeitet werden kann. Der Server liest das Limit aus dem verifizierten Rechtsanspruch, wenn er entscheidet, ob er ein weiteres Geräte-Lease ausstellt.
Das Geräte-Lease ist eine zweite, separat signierte Nutzlast, die den Rechtsanspruch referenziert:
public sealed record DeviceLease
{
public required string Sub { get; init; } // same subject as the entitlement
public required string Device { get; init; } // device id derived from the fingerprint
public required string Product { get; init; }
public required string Tier { get; init; }
public required DateTimeOffset NotAfter { get; init; } // SHORT: hours/days
public IReadOnlyList<string> Features { get; init; } = Array.Empty<string>();
}
Die laufende Anwendung verifiziert immer nur ein DeviceLease. Sie sieht die Abonnementlaufzeit nie direkt; sie sieht eine kurze Ablaufzeit, die sie zwingt, periodisch zum Server zurückzukehren, wo der langlebige Rechtsanspruch neu geprüft wird.
Eine Anmeldung in ein Geräte-Lease verwandeln
Das Gerät berechnet einen Fingerabdruck (dasselbe Maschinen-Identitätsmaterial, das Sie für Node-Locking verwenden würden), hasht ihn zu einer kompakten Geräte-ID und fordert ein Lease beim Server an. Die Anfrage trägt die authentifizierte Identität des Nutzers und die Geräte-ID; die Antwort ist ein signiertes DeviceLease oder eine Ablehnung.
public async Task<LeaseResult> RequestLeaseAsync(
AuthenticatedUser user, CancellationToken ct)
{
// Device id is a stable, non-reversible hash of the fingerprint inputs.
string deviceId = DeviceId.Compute(MachineFingerprint.Collect());
var resp = await _client.PostAsJsonAsync("v1/lease", new LeaseRequest
{
Product = "acme-tool",
Device = deviceId
}, ct); // user identity travels on the authenticated channel, not in the body
if (resp.StatusCode == HttpStatusCode.Conflict)
{
// Cap is full: the server tells us which devices hold the slots
// so the user can choose one to release.
var full = await resp.Content.ReadFromJsonAsync<SeatFullResponse>(ct);
return LeaseResult.SeatFull(full!.Devices);
}
resp.EnsureSuccessStatusCode();
var lease = await resp.Content.ReadFromJsonAsync<SignedLease>(ct);
return LeaseResult.Issued(lease!);
}
Serverseitig ist der Lease-Endpunkt die Stelle, an der die beiden Anker aufeinandertreffen. Er löst den Rechtsanspruch für das authentifizierte Subjekt auf, prüft, ob das Abonnement aktiv ist, und setzt dann das Gerätelimit durch, indem er verschiedene registrierte Geräte zählt und für ein Gerät, das bereits einen Slot hält, neu ausstellt, statt einen neuen zu verbrauchen:
public async Task<IResult> IssueLease(LeaseRequest req, ClaimsPrincipal caller)
{
string sub = caller.RequireSubject();
Entitlement ent = await _entitlements.GetActiveAsync(sub, req.Product)
?? return Results.Forbid(); // no live subscription → no lease
// A device that already holds a slot just gets a fresh lease (idempotent).
DeviceRegistration? existing = await _devices.FindAsync(sub, req.Product, req.Device);
if (existing is null)
{
int inUse = await _devices.CountAsync(sub, req.Product);
if (inUse >= ent.DeviceCap)
return Results.Conflict(await _devices.DescribeAsync(sub, req.Product));
await _devices.RegisterAsync(sub, req.Product, req.Device, DateTimeOffset.UtcNow);
}
else
{
await _devices.TouchAsync(existing.Id, DateTimeOffset.UtcNow); // last-seen
}
DeviceLease lease = new()
{
Sub = sub,
Device = req.Device,
Product = ent.Product,
Tier = ent.Tier,
Features = ent.Features,
NotAfter = DateTimeOffset.UtcNow.AddHours(12) // short, independent clock
};
return Results.Ok(_signer.Sign(lease)); // Keyright canonical-payload signing
}
Daraus folgen zwei Eigenschaften. Der Endpunkt ist idempotent pro Gerät: Ein wiederkehrendes Gerät stellt neu aus, statt einen Slot zu verbrauchen, sodass das Erneuern eines Lease nie einen Platz kostet. Und das Limit wird gegen das signierte DeviceCap durchgesetzt, gelesen aus dem Rechtsanspruch, den der Server aufgelöst hat – der Client hat nie ein Mitspracherecht, wie viele Geräte er registrieren darf.
Was der Client verifiziert
Die Anwendung verifiziert das Geräte-Lease genauso, wie sie jede Keyright-Lizenz verifizieren würde – sie stellt den öffentlichen Schlüssel des Signierers wieder her, verifiziert die Signatur über die kanonischen Bytes und liest erst dann die Claims. Der einzige Named-User-spezifische Schritt ist, dass sie die Geräte-ID des Lease gegen dieses Gerät prüft, damit ein für eine Maschine ausgestelltes Lease nicht auf einer anderen wiedergegeben werden kann, selbst unter demselben Konto:
public LicenseState Evaluate(SignedLease signed)
{
if (!_verifier.TryVerify(signed, out DeviceLease lease))
return LicenseState.Invalid("signature");
// The lease is bound to a device; confirm it is *this* device.
string thisDevice = DeviceId.Compute(MachineFingerprint.Collect());
if (!CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(lease.Device),
Encoding.UTF8.GetBytes(thisDevice)))
return LicenseState.Invalid("device-mismatch");
if (lease.NotAfter <= DateTimeOffset.UtcNow)
return LicenseState.Expired; // refresh against the server
return LicenseState.Active(lease.Tier, lease.Features);
}
Die Ablaufzeit hier ist die des Geräte-Lease – die kurze Uhr. Wenn sie verfällt, erneuert sich der Client; die Erneuerung führt die Rechtsanspruchsprüfung des Servers erneut aus, sodass ein ausgelaufenes Abonnement oder ein entferntes Gerät eine Ablehnung erzeugt und die Anwendung an der nächsten Grenze in ihren unlizenzierten Zustand fällt. Sie müssen nie ein Abschaltsignal in einen laufenden Client schieben; das kurze Lease ist der Kill-Schalter, und das Schweigen des Servers ist, was ihn auslöst.
Ein Gerät aus einem vollen Platz entfernen
Ein Gerätelimit ist nur menschlich, wenn der Nutzer einen Slot ohne Support-Ticket freimachen kann. Weil jedes Lease einen „Zuletzt gesehen”-Zeitstempel aktualisiert, kann die Voll-Platz-Antwort die Geräte des Nutzers mit genug Kontext auflisten, um eines zum Entfernen auszuwählen:
public async Task<IResult> ReleaseDevice(string device, ClaimsPrincipal caller)
{
string sub = caller.RequireSubject();
// Cooldown stops a shared account from churning devices to fake extra seats.
if (await _devices.RemovedRecentlyAsync(sub, TimeSpan.FromMinutes(30)))
return Results.StatusCode(StatusCodes.Status429TooManyRequests);
await _devices.RemoveAsync(sub, "acme-tool", device);
return Results.NoContent();
}
Die Abklingzeit ist das Detail, das das Limit bedeutsam hält. Ohne sie könnte ein geteiltes Konto alle paar Sekunden Geräte entfernen und wieder hinzufügen und faktisch unbegrenzt Maschinen betreiben; mit einer 30-Minuten-Untergrenze bei Entfernungen wird das Limit zu einer echten Decke, und Konto-Teilen schadet dem Teilenden – dessen eigene nächste Maschine ausgesperrt wird – weit mehr als Ihnen. Das aktuelle Lease des entfernten Geräts läuft bis zu seiner kurzen Ablaufzeit weiter und kann sich dann nicht erneuern, weil sein Slot verschwunden ist. Keine abrupte Abschaltung, kein feststeckender Platz.
Was Sie mitnehmen sollten
Node-Locking fragt „Welche Maschine ist das?”, und das ist die richtige Frage für Software, die pro Kiste verkauft wird. Die Named-User-Lizenzierung fragt „Wessen Platz ist das?” – die richtige Frage, wenn Sie an Menschen verkaufen, die legitim über mehrere Maschinen hinweg arbeiten. Verankern Sie den Rechtsanspruch an eine stabile Subjekt-ID mit dem Gerätelimit innerhalb der signierten Claims, leiten Sie kurzlebige Geräte-Leases ab, die die Geräte-ID tragen, damit sie nicht wiedergegeben werden können, und setzen Sie das Limit serverseitig durch, indem Sie verschiedene registrierte Geräte mit idempotenter Neuausstellung zählen, sodass eine Erneuerung nie einen Platz kostet. Geben Sie Nutzern eine Selbstbedienungs-Geräteentfernung mit Abklingzeit, und lassen Sie das kurze Lease Ihr Widerrufsmechanismus sein, damit Sie nie in einen laufenden Client eingreifen müssen. Tun Sie das, und ein Entwickler mit Laptop, Desktop und Build-Agent ist genau das, wofür er bezahlt hat: ein Platz. Für die Geräteidentitäts-Hälfte davon – wie der Fingerabdruck selbst aufgebaut wird – siehe Maschinen-Fingerabdruck und Gerätebindung, und für die signierte Nutzlast-Mechanik unter beiden Ankern, die Anatomie eines signierten Lizenzschlüssels.
Nebula.NET testen
Härten Sie Ihren .NET-Code in wenigen Minuten — starten Sie mit der kostenlosen Edition.