Kolumne

EnterpriseTales: Autorisierung mit dem JSON Web Token in Enterprise Java

Arne Limburg

© S&S_Media

Authentifizierung mit dem JSON Web Token ist mittlerweile State of the Art in verteilten Systemlandschaften. Aber wie sieht es beim Thema Autorisierung aus? Diese Kolumne wirft einen Blick darauf, wie und in welchem Umfang Autorisierung mit dem JSON Web Token in Spring (Boot) und MicroProfile möglich sind und wo es Grenzen gibt.

Authentifizierung mit dem JSON Web Token ist mittlerweile State of the Art in verteilten Systemlandschaften. Aber wie sieht es beim Thema Autorisierung aus? Diese Kolumne wirft einen Blick darauf, wie und in welchem Umfang Autorisierung mit dem JSON Web Token in Spring (Boot) und MicroProfile möglich sind und wo es Grenzen gibt.

Standardisierte Informationen im JWT

Bei der Authentifizierung geht es im Gegensatz zur Autorisierung darum, den Benutzer zu identifizieren. Es gibt verschiedene Arten dieser Identifizierung. Die einfachste und häufigste ist die Identifizierung über Benutzername und Kennwort. Wurde der Benutzer von einem Authentication Server identifiziert, müssen die gewonnenen Informationen zum Applikationsserver übermittelt werden. Bei den Verfahren mit JSON Web Token geschieht das dadurch, dass der Authentication Server für den Benutzer ein solches Token ausstellt und dem Benutzer übermittelt. Dieser kann es dann an den Applikationsserver weiterreichen. Da der Authentication Server das Token signiert hat, kann der Applikationsserver diese Signatur überprüfen und so sicher sein, dass die Informationen aus dem Token tatsächlich vom Authentication Server stammen.

Dabei kann ein JWT theoretisch beliebige Informationen enthalten, allerdings gibt es einige Standardinformationen (Claims), die enthalten sein sollten, wie Subject (sub), Issuer (iss) und Expiration Time (exp).

Autorisierung in verteilten Architekturen

Im Gegensatz zur Authentifizierung geht es bei der Autorisierung zum einen darum, zu klären, ob der aktuelle Benutzer eine Aktion durchführen darf und zum anderen darum, die Aktion zu verhindern, wenn er es nicht darf (Access Control). Eine gängige Art, die Rechte von Benutzern festzulegen, ist die Einteilung von Benutzern in Benutzergruppen. Diesen werden dann Rollen zugeordnet, und für die Rollen werden Rechte vergeben.

In einer verteilten Systemlandschaft stellt sich bei der Wahl der Benutzergruppen zunächst die Frage, ob diese Gruppen global für alle Services gültig sind oder ob es spezifische Benutzergruppen gibt, die nur für bestimmte Services relevant sind. Häufig findet man eine Mischung aus beiden Situationen. Es gibt immer Benutzergruppen, die für alle Services relevant sind, aber auch solche, die nur für einzelne Services gelten.

W-JAX 2019 Java-Dossier für Software-Architekten

Kostenlos: Java-Dossier für Software-Architekten 2019

Auf über 30 Seiten vermitteln Experten praktisches Know-how zu den neuen Valuetypes in Java 12, dem Einsatz von Service Meshes in Microservices-Projekten, der erfolgreichen Einführung von DevOps-Praktiken im Unternehmen und der nachhaltigen JavaScript-Entwicklung mit Angular und dem WebComponents-Standard.

 

Mindestens für die globalen Benutzergruppen ist es sinnvoll, wenn sie zentral verwaltet werden. Ein existierender Authentication Server bietet sich hierfür geradezu an. Wie bereits erwähnt, ist es möglich, im JSON Web Token beliebige Informationen zu hinterlegen. Wenn die Verwaltung der Benutzergruppen also im Authentication Server passiert, ist es sinnvoll, diese Informationen in den ausgestellten Tokens zu hinterlegen. Der Applikationsserver kann sie von dort auslesen und für die Zugriffskontrolle verwenden. Zur Erinnerung: Der Authentication Server signiert das Token, sodass der Applikationsserver sicher sein kann, dass die Gruppeninformationen auch tatsächlich von dort kommen und nicht etwa vom Benutzer selbst hinzugefügt wurden.

Leider gibt es im JWT-Standard keine Definition, in welcher Form (also in welchem Claim) die Gruppeninformationen hinterlegt werden können. Wenn ein Authentication Server diese Informationen also im Token hinterlegen will, ist ihm freigestellt, wie er das tut. Listing 1 zeigt exemplarisch die Repräsentation, die der Keycloak-Server standardmäßig wählt. Durch das Beispiel wird bereits das Problem mit Autorisierung in verteilten Anwendungen deutlich: Da ein Standard für das Hinterlegen der Benutzergruppen fehlt, muss jeder Service, der diese Informationen nutzen will, an die Struktur des jeweiligen Authentication Servers angepasst werden. Ein Wechsel des Authentication Servers ist dann nur schwer möglich.

"realm_access": {
  "roles": [
    "customer", "power-user"
  ]
}

Der Authentication Server signiert das Token, so kann der Applikationsserver sicher sein, dass die Gruppeninformationen tatsächlich von dort kommen und nicht vom Benutzer.

Standardisiertes Gruppenattribut in MicroProfile

Im MicroProfile-JWT-Standard wird versucht, diese Lücke zu schließen, indem spezifiziert wird, dass MicroProfile-kompatible Implementierungen neben den Claims aus dem RFC7519 auch zwei weitere Claims unterstützen müssen, die durch MicroProfile festgelegt werden. Die beiden Claims sind upn, das den Namen repräsentiert, den später das java.security.Principal erhält, und groups, das die Liste mit Benutzergruppen enthält. Auf den ersten Blick erscheint diese Standardisierung zwar eine gute Idee zu sein, betrachtet man diese Spezifikationen allerdings aus der Sicht der Makroarchitektur einer (Micro-)Services-Landschaft, wird ein Missmatch deutlich. Die Spezifikation dieser Claims in MicroProfile impliziert die Erwartung, dass sich der Authentication Server der Service-Landschaft an die MicroProfile-Spezifikation hält. Bereits das kann eine unüberwindbare Hürde sein in einer Organisation, in der das für den Authentication Server verantwortliche Team keinen Bezug zu Java, geschweige denn zu MicroProfile hat. Selbst wenn das Team des Authentication Servers überzeugt werden kann, impliziert die Spezifikation, dass sich die gesamte Service-Landschaft (in diesem Punkt) an das MicroProfile hält, unabhängig davon, ob sie ihren jeweiligen Applikationsserver (was ja z. B. ein einzelner Microservice sein kann) mit dem MicroProfile umsetzen. In einer solchen Service-Landschaft gibt es ggf. nicht nur Java-Services – und selbst bei solchen ist nicht zwingend gegeben, dass sie mit MicroProfile realisiert sind. Spring-basierte Services dürften, sofern es keine unternehmensweite Vorgabe gibt, nach wie vor die Mehrheit bilden.

Warum also sollte sich die Securityinfrastruktur einer solchen Service-Architektur nach dem MicroProfile-Standard richten?

Einbindung von Gruppen mit MicroProfile

Sollte das verwendete JWT die genannten Attribute dennoch enthalten, kann man in einem MicroProfile-basierten Service die JWT-Authentication über @LoginConfig mit Attribut authMethod = „MP-JWT“ aktivieren. Von da an lässt sich in dem Service die aus Java EE bekannte Annotation @RolesAllowed zur Access Control verwenden, um zu markieren, welche Teile des Codes von welcher Benutzerrolle ausgeführt werden dürfen. Um die Benutzergruppen aus dem Token auf die Rollen des jeweiligen Service zu mappen, kann das Group-Role-Mapping verwendet werden, das auch aus Java EE bekannt ist. Standardmäßig ist es ein 1:1-Mapping, d. h. die Benutzergruppen entsprechen den Rollen.

Einbindung vom Gruppen in Spring Security

Im Spring Framework ist das Teilprojekt Spring Security für die Verarbeitung von JSON Web Token verantwortlich. Auch hier gibt es ein vordefiniertes Claim, in dem die Rolleninformationen erwartet werden. Hier heißt er aber authorities. Damit wird das oben beschriebene Problem erneut direkt deutlich. Gibt es in einer Service-Landschaft sowohl Spring- als auch MicroProfile-Services, stellt sich unmittelbar die Frage, in welchem Claim die Gruppeninformationen hinterlegt werden sollen: Für MicroProfile-Services wäre groups passend, für Spring-Services authorities. Für Services, nicht nicht auf Java basieren, sind weitere Claims oder ganz andere Strukturen denkbar, wie Listing 1 zeigt.

Ermitteln von Rolleninbformationen aus anderen Claims

Enthält das JSON Web Token die Gruppeninformationen nicht im Claim groups, gibt es im MicroProfile-JWT-Standard keine Möglichkeit, diese Rolleninformationen aus dem Token zu extrahieren und sie dann in @RolesAllowed zu verwenden. Im Gegensatz dazu lässt sich Spring zumindest soweit konfigurieren, dass die Gruppeninformationen aus anderen Claims des Tokens ermittelt werden können, auch wenn es etwas aufwendiger ist. Möchte man das in MicroProfile erreichen, ist man auf herstellerspezifische Konfigurationen angewiesen. Zumindest im IBM-Liberty-Server gibt es eine solche Konfigurationsmöglichkeit aber auch. Die Variante erlaubt aber nur ein Mapping auf andere Claims und nicht die Verarbeitung solch komplexerer Strukturen wie in Listing 1.

Andere und/oder detailliertere Autorisierungsmethoden

Bisher haben wir hier nur von rollenbasierten Access-Control-Mechanismen gesprochen. Häufig gehen die Securityanforderungen einzelner Services aber darüber hinaus. Access Control Lists für einzelne Objekte oder sogar feldbasierte Zugriffsrechte werden benötigt. Immer wieder wird mir die Frage gestellt, ob diese Informationen auch im JWT-Token hinterlegt werden sollten. Das ist meist keine gute Idee, wenn man bedenkt, dass diese Informationen Service-spezifisch sind und jeder Service seine Informationen dann dort hinterlegen würde. Die Idee scheitert schon an der Größe des Tokens. Zwar gibt es keine offizielle Maximalgröße eines JWTs, allerdings wird es ja per HTTP übertragen. Und da verbietet es sich von vornherein, große Datenstrukturen zu verwenden. Außerdem gibt es bei den meisten Servern tatsächliche Obergrenzen, wie groß z. B. der Header-Bereich eines HTTP-Requests insgesamt sein darf.

Auf die weiteren Gründe, warum es keine gute Idee ist, detaillierte Service-spezifische Autorisierungsinformationen im JWT zu hinterlegen, möchte ich an dieser Stelle nicht eingehen.

So bleiben also nur zwei Möglichkeiten, wo besagte Securityinformationen in einer Service-Architektur hinterlegt werden könnten. Die erste mögliche Variante ist, dass jeder Service seine eigenen Securityinformationen speichert. Aus dem JWT würde er dann nur den Benutzernamen beziehen und die Rechte darauf basierend aus seiner eigenen Datenbank (oder einer anderen Quelle) auslesen. Diese Möglichkeit hat als Konsequenz, dass ebenso jeder Service eine eigene Möglichkeit bieten muss, die Zugriffsinformationen zu administrieren.

Benötigt man eine zentrale Administration dieser Informationen, ist die sinnvollste Möglichkeit, einen zentralen Administrations-Service zum Verwalten und Speichern der Zugriffsinformationen zu schaffen. Jeder Applikations-Service sollte dann allerdings den für ihn relevanten Teil dieser Informationen cachen, um Aufrufe des Administrations-Service bei jedem Request zu verhindern. Wie bei jedem Cache muss dann natürlich über eine geeignete Invalidierungsstrategie nachgedacht werden.

Fazit

Im JWT-Standard fehlt eine Festlegung, wie Benutzergruppen, Rollen und Rechte im Token hinterlegt werden sollen. Zwar ergänzt der MicroProfile-Standard das JWT um ein entsprechendes Attribut, doch nicht alle (Micro-)Service(s)-Architekturen können oder wollen sich nach dem MicroProfile-Standard richten, insbesondere dann, wenn es sich gar nicht um Java-Services handelt. Dass der MicroProfile-Standard dieses Claim überhaupt standardisiert, ist daher wenig zielführend. Besser wäre es gewesen, eine Möglichkeit zu standardisieren, um die Gruppeninformationen aus einem bestehenden Token auszulesen und dem Applikationsserver zur Verfügung zu stellen, unabhängig davon, in welcher Struktur sie im Token vorliegen. Spring bietet eine solche Möglichkeit.

Wie bereits beim Tracing [2] missachtet die MicroProfile Spec die polyglotte Natur von (Micro-)Service-Architekturen. Beim Erstellen der Spec wird davon ausgegangen, dass sich die gesamte Makroarchitektur nach der MicroProfile Spec richtet. Sinnvoller wäre es allerdings, davon auszugehen, dass MicroProfile Services nur eine Teilmenge der Services einer Architektur ausmachen. Bezogen auf das JWT wäre es daher sinnvoller, die Integration einer bestehenden JWT-Struktur in einen MicroProfile Service zu standardisieren, anstatt zu versuchen, die JWT-Struktur selbst zu standardisieren (Dafür gibt es andere Gremien). Vielleicht fließt eine solche Möglichkeit ja in eine der nächsten Versionen des MicroProfile-Standards ein.

In diesem Sinne – Stay Tuned.

Geschrieben von
Arne Limburg
Arne Limburg
Arne Limburg ist Softwarearchitekt bei der open knowledge GmbH in Oldenburg. Er verfügt über langjährige Erfahrung als Entwickler, Architekt und Consultant im Java-Umfeld und ist auch seit der ersten Stunde im Android-Umfeld aktiv.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
4000
  Subscribe  
Benachrichtige mich zu: