Doping für Java-EE-basierende Microservices

So werden Java-Anwendungen fit für Microservices

Michael Hofmann

©Shutterstock / bitt24

Die Implementierung von Microservices bringt einige neue Herausforderungen mit sich. Als Architekt muss man sich deswegen verstärkt mit der Anwendungskonfiguration, Resilienz und Fehlertoleranz (aufgrund des Kommunikationsverhaltens der Microservices untereinander) sowie Security und Laufzeitüberwachung beschäftigen, um nur ein paar Anforderungen zu nennen. Um die Implementierung auch mit Java-EE-Mitteln zu ermöglichen, wurde die MicroProfile-Community gegründet.

MicroProfile JWT Propagation

MicroProfile JWT Propagation

Für eine Microservices-Anwendung, die in der Regel aus zustandslosen Services besteht, ist es notwendig, dass Securityinformationen bei jedem Aufruf (auch untereinander) mitgeschickt werden. Der aufgerufene Service sollte nach erfolgreichen Überprüfungen der Authentifizierung und Berechtigung einen Securitycontext für den Aufrufer erstellen. Die Sicherheit in Java EE basiert seit Jahren auf diesem Securitycontext und bietet mit der Role Based Access Control (RBAC) eine entsprechende Zugriffsprüfung an. Stellt sich also nur noch die Frage, wie die Securitydaten übertragen werden sollen und wie sie in den Java-EE-Securitycontext einfließen. Für die Übertragung kommen mithilfe von OpenID Connect (OIDC), basierend auf JSON Web Tokens (JWT), die Securitytokens ins Spiel. Ein JWT-basierender Securitytoken in Form einer JSON-Struktur besitzt mehrere Claims, in denen die Securitydaten des Aufrufers hinterlegt sind. Um zu verhindern, dass Angreifer diese Daten verändern, wird das JWT-Token mit der JSON Web Signature signiert (JWS). Somit lässt sich prüfen, ob der Aussteller des JWT-Tokens (issuer) auch wirklich bekannt ist und die Daten auf dem Transportweg nicht manipuliert wurden. Nach erfolgreichen Prüfungen überträgt der Applikationsserver die Werte aus dem JWT-Token in den Java-EE-Securitycontext und delegiert die nachfolgenden Zugriffsprüfungen auf Java-EE-Ressourcen an die Java-EE-Security.

Die Verwendung von JWT bringt einige Vorteile mit sich. Die JSON-Struktur lässt sich beliebig erweitern. Ohne Beeinflussung der anderen Claimwerte und durch die Übertragung aller securityrelevanten Daten ist kein weiterer Roundtrip zum ausstellenden Authorization-Server notwendig. Auch das lästige Cross-Origin-Resource-Sharing-Problem (CORS) entfällt, da keine Cookies zum Einsatz kommen. Im Detail laufen folgende Schritte ab. Der Aufrufer überträgt im HTTP-Authorization-Header das JWT-Token als Bearer-Token:

Authorization: Bearer <JWT-Token>

Ein signiertes JWT-Token besteht aus drei Elementen: Header, Body und Signatur, wobei die einzelnen Elemente mit Punkt voneinander getrennt werden:

eyJ0eXAiOiJKV1QiLCJraWQiOiJteW9wa2V5IiwiYWxnIjoiUlMyNTYifQ.eyJqdGkiOiJ4eXotMTIzNDUiLCJzdWIiOiJ1c2VyMSIsImdyb3VwcyI6WyJhZG1pbiIsIm5vcm1hbC11c2VyIl0sImlzcyI6Imh0dHBzOi8vc2VydmVyLm15Y29tcGFueS5kZSIsImV4cCI6MTUwNzEwMzI0OCwiaWF0IjoxNTA3MTAzMDQ4LCJ1cG4iOiJ1c2VyMUBteWNvbXBhbnkuZGUifQ.rTM_NemKw5fpXnuh_q6pwF0GaoRzqgoz-_TUsCdIKdcpFEsWs_Ro_aQ6YfYMy178JYy-LtUylTWrSus67nS820YzWHjSYmNReKxhBAzWw48c0a1A8tAMJ594aVnSyVs2_kG09nvKyf5f5JPvpbJyIULOZIDYC2zDx3paKJsJmg4

Inhalt dieses JWT-Tokens:

{
  "typ": "JWT",
  "kid": "myopkey",
  "alg": "RS256"
}
{
  "jti": "xyz-12345",
  "sub": "user1",
  "groups": ["admin", "normal-user"],
  "iss": "https://server.mycompany.de",
  "exp": 1507103248,
  "iat": 1507103048,
  "upn": "user1@mycompany.de"
}

Der aufgerufene Microservice extrahiert das JWT-Token aus dem HTTP-Authorization-Header und führt mehrere Prüfungen aus. Dazu gehört die Validierung der Signatur mittels JOSE-Header (der Claim alg gibt den Signierungsalgorithmus an; derzeit ist nur RS256 möglich), die Kontrolle, ob Tokenaussteller (iss: issuer) bekannt und somit vertrauenswürdig ist und die Prüfung der zeitlichen Gültigkeit des Tokens (iat: issue at, exp: expires; Sekunden seit 01.01.1970). Um einen höheren Schutz vor Missbrauch durch einen Angreifer gewährleisten zu können, sollte der Time-out im JWT-Claim exp möglichst kurz gehalten werden. Nachdem das empfangene JWT-Token erfolgreich validiert worden ist, werden die Informationen aus dem Token in das Java EE Subject übertragen und damit der Java-EE-Securitycontext erstellt. Neben den Standardclaims aus dem JWT sind hier noch zwei weitere Claims zu erwähnen: upn (User Prinicpal Name) – menschenlesbarer Wert, der den User Principal identifiziert, und groups – Gruppenzugehörigkeiten des Subject, die analog dem Java-EE-Securitymechanismus den Securityrollen zugeordnet werden.

Wie wird nun der REST-basierende Microservice mithilfe des JWT sicher gemacht? Dazu führt der Entwickler eine neue Annotation org.eclipse.microprofile.annotation.LoginConfig ein, die auf die REST-Applikation angewandt werden muss:

@LoginConfig(authMethod = "MP-JWT", realmName = "MyRealmName")
@ApplicationPath("/api")
public class MyApplication extends javax.ws.rs.core.Application {...}

Damit wird der Microservice angewiesen, auf die MicroProfile-JWT-Securitymethode (MP-JWT) umzustellen. Jetzt kann mit den üblichen Java-EE-Securityannotationen, wie z. B. @RolesAllowed gearbeitet werden, da die Gruppen aus dem groups-Claim automatisch mit den Rollen verknüpft werden. Um die Informationen aus dem JWT in den java.security.Principal zu übertragen, führt die MicroProfile-Spezifikation ein neues Interface ein:

public interface org.eclipse.microprofile.jwt.JsonWebToken extends java.security.Principal

welches sich der Entwickler mittels

@Inject
private JsonWebToken myCallerPrincipal;

in die Anwendung injizieren lassen kann. Somit wird der Zugriff auf einzelne Claimwerte ohne ein manuelles Parsen des JWT-Tokens ermöglicht. Ab hier unterscheidet sich die MicroProfile-Anwendung nicht mehr von einer herkömmlichen Java-EE-Securityanwendung. Die Anpassungen, die mit JSR 375 (Java EE Security API für Java EE 8) in Java EE 8 gerade dazugekommen sind, sind schon berücksichtigt.

Ein weiteres nützliches Feature wäre das Tracing. Dahinter steckt, die Aufrufkette innerhalb des Microservices-Netzes bezogen auf den jeweiligen Principal zu protokollieren, um das Aufrufverhalten besser analysieren zu können. Auch für Fehlersuchen ist diese Informationsquelle sehr hilfreich. Die Claims aus dem JWT, im Speziellen des Claims jti (JWT Identifier), würden hierfür die notwendigen Informationen enthalten. Bleibt also abzuwarten, was die geplante Spezifikation MicroProfile OpenTracing in kommenden Releases hierfür mit sich bringen wird.

Geschrieben von
Michael Hofmann
Michael Hofmann
Michael Hofmann ist freiberuflich als Berater, Coach, Referent und Autor tätig. Seine langjährigen Projekterfahrungen in den Bereichen Softwarearchitektur, Java Enterprise und DevOps hat er im deutschen und internationalen Umfeld gesammelt. Mail: info@hofmann-itconsulting.de
Kommentare

Hinterlasse einen Kommentar

avatar
4000
  Subscribe  
Benachrichtige mich zu: