JSON Web Token (JWT) im Detail

Keine Kommentare

JWT ist die Abkürzung für “JSON Web Token”, das für den Einsatz im Web konzipiert wurde. Typisches Einsatzgebiet für JWT ist die fortlaufende Authentifizierung bei SSO (“single sign-on”), JWT ist aber flexibel einsetzbar für alle Bereiche, in denen kompakte, signierte und u. U. verschlüsselte Informationen übertragen werden müssen. Dieser Artikel gibt einen Überblick über den Aufbau und die Nutzung von JWT.

Eine konkrete Implementierung gibt es beispielsweise bei Atlassian Connect. Die Cloud-Lösung für Produkte wie JIRA, Confluence, Bamboo und andere nutzt JWT zur Kommunikation mit Add-ons von Drittanbietern. JWT wird hier eingesetzt, um Requests zu authentifizieren und die Integrität der Anfrage sicherzustellen. Einen Überblick über Atlassian Connect bietet unser Artikel über die Sicherheit von Atlassian.

Aufbau eines JWT-Token

Das JWT ist ein einfacher String, der als Request-Parameter oder im Header übertragen wird. Ein JWT hat den folgenden Aufbau:

HEADER.PAYLOAD.SIGNATURE

Ein JWT-Token besteht also aus drei Teilen, die durch Punkte (.) voneinander getrennt sind. Hier sind beispielhafte Belegungen dieser Teile:

  • Header: “eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
  • Payload: “eyJpZCI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoiTWFydGhhIFRlc3RlciJ9
  • Signatur: “AfF8fGzbhhpS9k-rgJt7RlZaUnP9phwmnPTku2fs4o0”

Der Header beschreibt den Signatur- und/oder Verschlüsselungsalgorithmus und den Token-Typ. Die Daten werden im JSON-Format abgelegt und Base64-kodiert. Das vorliegende Beispiel sieht im Klartext so aus:

{"alg":"HS256","typ":"JWT"}

Im Beispiel wurde die Payload des JWT mit HMAC SHA256 (abgekürzt “HS256”) signiert.

Als Payload wird das JSON-Objekt bezeichnet, das aus einer “beliebigen” Anzahl von Key/Value-Paaren besteht. Diese Key/Value-Paare werden ”Claims” genannt (siehe unten). Der Payload  wird ebenfalls Base64-kodiert. Im Beispiel hat der Payload folgenden Inhalt:

{ "id": "1234567890", "name": "Martha Tester" }

Das letzte Element ist die Signatur, die aus dem Header und der Payload berechnet wird. Der Algorithmus kann in Pseudocode wie folgt beschrieben werden:

content = base64UrlEncode(header) + "." + base64UrlEncode(payload);
signature = HMACSHA256(content, secret);

Auch die daraus entstehende Signatur wird ebenfalls Base64-kodiert und bildet den dritten Abschnitt des JWT.

Payload und Claims

Wie bereits erwähnt, enthält der Payload eine beliebige Anzahl von Key/Value-Paaren. Der Key wird in diesem Kontext als “Claim” bezeichnet. Prinzipiell darf der Payload beliebig viele Claims beinhalten. Natürlich wächst das JWT mit jedem Element und benötigt dann auch mehr Ressourcen bei der (De-)Kodierung. Es empfiehlt sich daher, die Informationen im JWT stark zu begrenzen.

Claims können in drei verschiedene Typen aufgeteilt werden: registrierte, öffentliche und private Claims.

Registrierte Claims

Es handelt sich hierbei um Keys, deren Zweck in einem Standard festgelegt ist. Wichtig: Die Spezifikation definiert, dass alle Claims optional sind! Applikationen können also frei wählen, ob sie diese Felder nutzen. Wenn sie genutzt werden, sind öffentliche Claims wie folgt zu nutzen (Auszug):

  • “iss” (Issuer): identifiziert die Einheit, die das JWT ausgegeben hat; Beispiel: die ID einer bestimmten JIRA-Applikation
  • “sub” (Subject): Dieses Feld wird dazu genutzt, den Nutzer, auf den sich eine Aktion bezieht, zu identifizieren; Beispiel: der Nutzer, der in JIRA eine Aktion ausgelöst hat
  • “aud” (Audience): identifiziert potenzielle Empfänger des JWT
  • “exp” (expiration time): der Zeitpunkt, zu dem das JWT-Token nicht mehr akzeptiert werden darf
  • “jti” (JWT ID): Dieser Wert ist dazu gedacht, Events zu markieren, die nur einmal verarbeitet werden sollen. Wie diese ID im konkreten Fall gespeichert und zur (In-)Validierung eines Events genutzt wird, ist die Aufgabe der Applikation selbst.

Weitere Claims sind im RFC 7519 beschrieben.

Öffentliche (“Public”) Claims

Diese Claims bezeichnen Key/Values, die zusätzlich zum Standard benutzt werden können. Um Kollisionen in der Semantik der Keys ausschließen zu können, sind diese Claims öffentlich registriert. Das Registrierungsverfahren ist in einem RFC 7519  beschrieben. Einen Überblick über öffentliche Claims gibt IANA.

Private Claims

Hierbei handelt es sich um Keys, die bei verschiedenen JWT-Nutzungen eine unterschiedliche Semantik aufweisen können. Die Keys sind für Parteien gedacht, die über JWT kommunizieren wollen und für ihren Anwendungsfall einen Key explizit vereinbaren wollen. Die Namen sind nicht öffentlich registriert (siehe auch IANA).

Vertraulichkeit und Datenintegrität

Das JWT basiert auf zwei weiteren Standards, die zur sicheren Kommunikation im Web gesetzt wurden. Dies ist zum einen JWS (JSON Web Signature), ein Schema, das die Signierung von Nachrichten regelt, zum anderen JWE-Schema (JSON Web Encryption), das zur Verschlüsselung von JWT genutzt wird. Je nach Sensibilität der Daten müssen eines oder beide Verfahren für das JWT umgesetzt werden.

Im Zentrum dieser Betrachtung stehen die Claims, die im Payload untergebracht sind. Egal, welche Stufe eingesetzt wird, es empfiehlt sich in jedem Fall, SSL für die Kommunikation zu nutzen.

Keine Sicherung

Für den Fall, dass die Daten keinen hohen Schutzcharakter aufweisen, kann im Header als Verschlüsselungsalgorithmus “None” angeben werden. In diesem Fall muss keine Signatur generiert werden. Der Payload kann nach der Base64-Entschlüsselung im Klartext gelesen werden. Es gibt keine Aussage darüber, ob die Nachricht vom richtigen Absender kam und ob die Nachricht verändert wurde.

Nur Signatur (JWS)

Für die meisten Fälle reicht es aus, sicherzustellen, dass die Daten vom richtigen Absender kommen und nicht verändert wurden. Der Payload kann auch hier nach der Base64-Entschlüsselung im Klartext gelesen werden. Das JWT enthält jedoch nun auch eine Signatur, die die Integrität der Nachricht sicherstellt. Das Verfahren ist das, welches eingangs im Detail vorgestellt wurde. Der RFC 7519 fordert, dass mindestens HMAC-SHA256 (“HS256”) implementiert wurden, er empfiehlt außerdem die Implementierung von  RSA-SHA256 (“RS256”) und  ECDSA-SHA256 (“ES256”). Der Empfänger weiß nun, wer die Nachricht versandt hat und dass die Nachricht nicht verändert wurde. Die Payload kann jedoch nach der Base64-Entschlüsselung im Klartext gelesen werden (keine Vertraulichkeit).

Signatur (JWS) und Verschlüsselung (JWE)

In der letzten Stufe werden die Inhalte des Claims selbst verschlüsselt (JWE-Schema) und zusätzlich über  das JWS-Schema signiert. Die Inhalte können dann nur noch über das gemeinsame Kennwort oder den privaten Schlüssel entschlüsselt werden.

Bibliotheken können dieses Level optional unterstützen. Sollte dies der Fall sein, sind nur die folgenden Standards Pflicht: RSAES-PKCS1-v1_5 mit 2048-Bit-Schlüssel („RSA1_5“), AES Key Wrap mit 128- and 256-Bit-Schlüssel („A128KW“ und „A256KW“) sowie AES-CBC/HMAC SHA-2 („A128CBC-HS256“ und „A256CBC-HS512“). Der Absender ist nun authentifiziert, die Nachricht ist authentisch und vertraulich.

Gültigkeitsdauer eines JWT

Die Gültigkeit eines JWT ist begrenzt, wenn der registrierte Claim “exp” benutzt wird. Dieser enthält einen Zeitstempel. Der RFC 7519 schreibt vor, dass das Token nicht mehr weiter verarbeitet werden darf, wenn dieser Zeitpunkt überschritten wurde. Das JWT ist dann als ungültig anzusehen.

Wie wird das JWT genutzt

Da JWT erst sinnvoll ist, wenn eine Signatur verwendet wird, muss vor der eigentlichen Nutzung ein Schlüssel (der RFC 7519 spricht vom “secret”) festgelegt werden. Dieser kann – z.B. als Antwort auf ein Login – zwischen den Kommunikationspartnern ausgetauscht werden. Asymmetrische Verfahren werden auch unterstützt, es werden dann die öffentlichen Schlüssel ausgetauscht. Der Schlüssel sollte mindestens über eine gesicherte HTTP-Verbindung (HTTPS) übertragen werden, um ein einfaches Auslesen zu vermeiden. Noch besser ist natürlich der Gebrauch asymmetrischer Verfahren.

Mit dem Austausch des Schlüssels kann nun die Kommunikation zwischen den beiden Partnern durchgeführt werden. Dazu generiert die Applikation für ihren Request den vereinbarten JWT mit dem Schlüssel des Partners und liefert das JWT als Parameter (z.B. ‘jwt’ bei GET-Requests) oder als “Authorization”-Header (bei POST, PUT, OPTIONS, DELETE) an den Partner aus. Atlassian Connect beispielsweise zeigt auf deren Supportseite, wie die Kommunikation mit GET und POST aufgebaut sein kann.

Der Kommunikationspartner kann den Request entgegennehmen, das JWT entschlüsseln und dessen Integrität überprüfen. Ist dieser Schritt erfolgreich, kann die erwünschte Anfrage ausgeführt werden. Bei Antworten wird dasselbe Verfahren angewendet. Das JWT kommt also bei jedem einzelnen Request zum Einsatz.

Sicherheitslücke

Leider weist das JWT eine Sicherheitslücke auf: Der RFC 7519 definiert, dass eine Bibliothek eine bestimmte Anzahl von Signatur- und Verschlüsselungs-Algorithmen bedienen muss. Dazu gehört auch der “None”-Algorithmus für unsignierte JWT. Leider beschreibt der Standard nicht, wann und wie der “None”-Algorithmus NICHT genutzt werden darf. Bei der Nutzung von JWT ist es daher notwendig, zu überprüfen, dass tatsächlich eine korrekte Signatur erstellt wurde, um manipulierte JWTs zu vermeiden. Die schließt auch die Kontrolle des Headers auf einen korrekten Signatur-Algorithmus ein. Weitere Details liefert dieser Bericht.

Zusammenfassung

JWT ist sehr gut für den Austausch von Nachrichten in gesicherten Umgebungen zwischen zwei Anwendungen geeignet. Die angebotenen Mechanismen stellen die Authentizität und Integrität von Nachrichten sicher, sensible Daten können optional verschlüsselt werden.

Theoretisch könnten JWT gänzlich ohne Sicherung genutzt werden. In der Praxis empfiehlt es sich jedoch, mindestens die Verbindung per SSL zu sichern und die Nutzdaten (“Payload”) des JWT zu signieren. Für besonders sensible Daten sollten die Daten im Payload außerdem verschlüsselt werden. Es gibt eine bekannte Sicherheitslücke, die durch eine eigene Überprüfung des genutzten Algorithmus leicht umgangen werden kann.

Für die Implementierung des JWT muss man nicht mehr selbst Hand anlegen, es gibt im Netz diverse Implementierungen für die unterschiedlichsten Sprachen. Bei JWT.io findet man unter dem Menüpunkt “Libraries” einen Überblick.

Oliver Hoogvliet

Oliver arbeitet seit 20 Jahren in der Softwareentwicklung. Seine Begeisterung für die agile Softwareentwickung entwicklelte sich durch die Arbeit in unterschiedlichen Rollen in agilen Teams. Aktuell ist er als Senior IT Consultant bei der codecentric AG tätig. Seine Schwerpunktthemen sind agile Softwareentwicklung, agile Methodiken, Continuous Delivery, Spring Boot, Java, Groovy und DevOps.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.