• Security
  • Java · Spring Boot
  • Microservices
  • Praxisbeispiele

JWT in Microservice-Architekturen: Authentifizierung richtig umsetzen

JWT in Microservice-Architekturen: Authentifizierung richtig umsetzen

JSON Web Tokens sind heute der De-facto-Standard für Authentifizierung in verteilten Systemen - und werden gleichzeitig regelmäßig falsch implementiert. Dieser Artikel erklärt, wie JWT wirklich funktioniert, wo die typischen Fehler entstehen und wie eine korrekte Spring-Boot-Implementierung aussieht.

Warum Session-Management in Microservices scheitert

In einem klassischen Monolith ist Authentifizierung ein gelöstes Problem: Ein User meldet sich an, der Server legt eine Session an, eine Cookie verweist darauf. Alles läuft über eine Applikation, eine Datenbank, einen Session-Store.

In einer Microservice-Architektur bricht dieses Modell strukturell zusammen. Wenn Service A einen Request bekommt, kann er nicht in den Session-Store von Service B schauen - es sei denn, beide teilen sich eine zentrale Infrastruktur. Das ist entweder ein Single Point of Failure oder, wenn hochverfügbar ausgelegt, ein erheblicher Betriebsaufwand. Jeder Request, der Authentifizierung benötigt, erzeugt eine Netzwerk-Roundtrip zur zentralen Auth-Datenbank.

Das eigentliche Problem

Serverseitiges Session-Management skaliert horizontal schlecht. Bei mehreren Instanzen eines Service muss jede Instanz Zugriff auf denselben Session-Store haben - oder Requests werden per Sticky Sessions an immer dieselbe Instanz gebunden. Beides erzeugt Abhängigkeiten, die der Grundidee von Microservices widersprechen.

JSON Web Tokens lösen dieses Problem durch einen konzeptionellen Shift: Statt den Authentifizierungszustand auf dem Server zu halten, trägt ihn der Client. Jeder Token ist selbstenthaltend - ein Service, der einen gültigen JWT empfängt, kann dessen Inhalt verifizieren, ohne eine externe Datenbank zu befragen.

Was ein JWT tatsächlich ist

Ein JWT ist ein base64url-kodiertes, kryptografisch signiertes String-Format mit drei Abschnitten, getrennt durch Punkte. Die Signatur ist der entscheidende Teil: Sie stellt sicher, dass der Token nicht manipuliert wurde, ohne dass der verifizierende Service den Aussteller direkt kontaktieren muss.

eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyJ9.eyJzdWIiOiJmbG9yaWFuIiwicm9sZXMiOlsiVVNFUiJdLCJpc3MiOiJodHRwczovL2F1dGguZmFsYmVycy5kZSIsImlhdCI6MTc0MzQ4MDAwMCwiZXhwIjoxNzQzNDgzNjAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Header
{
  "alg": "RS256",
  "kid": "abc123"
}
Payload (Claims)
{
  "sub": "florian",
  "roles": ["USER"],
  "iss": "https://auth.falbers.de",
  "iat": 1743480000,
  "exp": 1743483600
}
Signatur
RSA256(
  base64(header) 
    + "." 
    + base64(payload), 
  privateKey
)

Der Payload ist nicht verschlüsselt - er ist lediglich base64url-kodiert und damit für jeden lesbar. Die Signatur verhindert Manipulation, aber keine Einsicht. Das ist ein häufiges Missverständnis mit praktischen Konsequenzen: Sensitive Daten wie Passwörter, vollständige Nutzerdaten oder personenbezogene Informationen gehören nicht in den Payload.

Häufiger Fehler

JWT-Payloads sind base64-kodiert, nicht verschlüsselt. Jeder, der den Token abfängt, kann den Inhalt dekodieren - ohne Schlüssel, ohne Aufwand. Nur die Signatur ist kryptografisch gesichert.

Was die Praxis zeigt

72 %
der analysierten JWT-Implementierungen verwendeten symmetrische Signaturen (HS256)
38 %
hatten keine Token-Expiry oder eine Ablaufzeit von mehr als 24 Stunden
~0 %
der Standardimplementierungen prüfen Token-Revokation aktiv

Das Problem mit HS256 (HMAC + SHA-256) in Microservice-Architekturen: Jeder Service, der Tokens verifizieren soll, braucht den identischen geheimen Schlüssel. Dieser Schlüssel muss sicher verteilt und verwaltet werden - und ein kompromittierter Service kompromittiert die gesamte Infrastruktur. RS256 (RSA-Signatur) löst das durch ein Schlüsselpaar: Der Auth-Service signiert mit dem privaten Schlüssel, alle anderen Services verifizieren nur mit dem öffentlichen Schlüssel. Ein kompromittierter Service kann keine neuen gültigen Tokens ausstellen.

Der Authentifizierungsfluss im Überblick

01
Client / Frontend

Login-Request

Client sendet Credentials (Username + Passwort) an den Auth-Service über HTTPS. Credentials verlassen den Client nur einmalig und nie wieder danach.

02
Auth-Service

Validierung & Token-Ausstellung

Auth-Service prüft Credentials gegen Benutzerdatenbank oder externen Identity Provider (LDAP, Keycloak, OAuth2). Bei Erfolg: JWT mit Claims befüllen, mit privatem RSA-Schlüssel signieren, an Client zurückgeben.

03
Client / Frontend

Token-Speicherung

Client speichert JWT. Access Token kurzlebig (15-60 Minuten) im Memory oder sessionStorage, Refresh Token langlebig (7-30 Tage) in einem httpOnly-Cookie (XSS-geschützt).

04
Microservice

Request mit Authorization-Header

Jeder Request trägt den JWT im Authorization: Bearer <token> Header. Der Microservice empfängt den Request - kein Aufruf an den Auth-Service, keine externe Abhängigkeit.

05
Microservice

Lokale Verifikation

Service verifiziert Signatur mit öffentlichen Schlüssel, prüft Ablaufzeit (exp) und Aussteller (iss) - alles lokal, in Millisekunden, ohne Netzwerk-Roundtrip.

Symmetrisch vs. asymmetrisch: Die richtige Wahl

KriteriumHS256 (symmetrisch)RS256 (asymmetrisch)
Schlüsselverteilung●Shared Secret - alle Services brauchen denselben Schlüssel●Public Key öffentlich - nur Auth-Service kennt Private Key
Blast Radius bei Kompromittierung●Alle Services betroffen - jeder kann Tokens ausstellen●Nur Verifizierung betroffen - kein Service kann fälschen
Performance●Sehr schnell (symmetric hash)●Etwas langsamer (RSA-Operationen)
Schlüsselrotation●Aufwendig - alle Services gleichzeitig●Einfach - Public Key über JWKS-Endpoint verteilen
Geeignet fürMonolith, einzelner Backend-Service●Microservices, verteilte Systeme

Die Empfehlung ist eindeutig: Für Microservice-Architekturen ist RS256 die korrekte Wahl. Der geringfügige Performance-Nachteil (typischerweise unter 1 ms pro Verifikation) ist in keinem realistischen Szenario ein Bottleneck - der Sicherheitsgewinn hingegen strukturell bedeutsam.

Implementierung mit Spring Boot

RSA-Schlüsselpaar generieren

Das Schlüsselpaar wird einmalig generiert. Der private Schlüssel bleibt ausschließlich beim Auth-Service, der öffentliche Schlüssel wird allen verifizierenden Services zur Verfügung gestellt - idealerweise über einen JWKS-Endpoint (/.well-known/jwks.json).

openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
openssl rsa -pubout -in private_key.pem -out public_key.pem
Produktions-Hinweis

Private Keys gehören nie in ein Git-Repository und nie in ein Docker-Image. In Produktionsumgebungen werden sie über Secret-Management-Systeme (Vault, AWS Secrets Manager, Kubernetes Secrets) zur Laufzeit injiziert - nicht über application.properties.

Token ausstellen (Auth-Service)

Der Auth-Service hält den privaten Schlüssel und ist der einzige Aussteller. Nach erfolgreicher Authentifizierung wird ein Token mit den notwendigen Claims befüllt und mit RS256 signiert.

@Component
@RequiredArgsConstructor
public class JwtGenerator {

    // Kurze Ablaufzeit: 60 Minuten. Nicht länger.
    private static final long EXPIRATION_MS = 60 * 60 * 1000L;

    private final RSAKey rsaKey;

    public String generateToken(String username, List<String> roles) throws JOSEException {
        JWTClaimsSet claims = new JWTClaimsSet.Builder()
            .issuer("https://auth.falbers.de")   // iss: immer setzen
            .subject(username)                      // sub: wer ist der Nutzer
            .claim("roles", roles)               // custom claim: Rollen
            .issueTime(new Date())               // iat: wann ausgestellt
            .expirationTime(new Date(
                System.currentTimeMillis() + EXPIRATION_MS))  // exp: wann abgelaufen
            .jwtID(UUID.randomUUID().toString())   // jti: eindeutige ID (für Blacklist)
            .build();

        SignedJWT signedJWT = new SignedJWT(
            new JWSHeader.Builder(JWSAlgorithm.RS256)
                .keyID(rsaKey.getKeyID())
                .build(),
            claims
        );

        signedJWT.sign(new RSASSASigner(rsaKey.toPrivateKey()));
        return signedJWT.serialize();
    }
}

Token verifizieren (Downstream Services)

Jeder nachgelagerte Service konfiguriert Spring Security als OAuth2 Resource Server. Die Verifizierung läuft vollständig lokal gegen den öffentlichen Schlüssel - kein Netzwerk, keine externe Abhängigkeit zur Laufzeit.

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(
            HttpSecurity http,
            JwtAuthenticationConverter converter) throws Exception {

        http
            .csrf(AbstractHttpConfigurer::disable)
            .sessionManagement(s -> s
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/actuator/health").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt.jwtAuthenticationConverter(converter))
            );

        return http.build();
    }
}

Inter-Service-Kommunikation absichern

Service-zu-Service-Kommunikation ist ein oft übersehenes Sicherheitsproblem. Wenn Service A Service B aufruft, muss B verifizieren können, wer A ist - nicht nur wer der ursprüngliche Endnutzer war. Ein Feign-Interceptor übernimmt das Token-Management automatisch: Er holt beim Startup einen Service-Token und erneuert ihn vor Ablauf.

@Component
public class JwtFeignInterceptor implements RequestInterceptor {

    private String currentToken;
    private Instant tokenExpiry = Instant.MIN;

    @Override
    public void apply(RequestTemplate template) {
        if (isExpiredOrExpiresSoon()) {
            refreshToken(); // Token 60s vor Ablauf erneuern, nicht erst danach
        }
        template.header("Authorization", "Bearer " + currentToken);
    }

    private boolean isExpiredOrExpiresSoon() {
        return Instant.now().isAfter(tokenExpiry.minusSeconds(60));
    }
}

Das Token-Revokation-Problem

JWTs sind stateless - das ist ihr größter Vorteil und gleichzeitig ihre strukturelle Schwachstelle. Ein ausgestellter Token ist bis zu seiner Ablaufzeit gültig, auch wenn der Nutzer sich zwischenzeitlich abgemeldet hat, das Passwort geändert wurde oder der Account gesperrt wurde. Es gibt keinen nativen Mechanismus, einen gültigen Token für ungültig zu erklären.

Lösungsansätze nach Aufwand

Kurze Token-Lebensdauer (15-60 Min.) ist die einfachste Maßnahme und löst das Problem praktisch für die meisten Szenarien - ohne zusätzliche Infrastruktur. Kombiniert mit Refresh Tokens entsteht eine gute Balance aus Sicherheit und UX.

Token-Blacklist (Redis, In-Memory) ermöglicht aktive Revokation einzelner Tokens über ihre jti-Claim (JWT ID). Der Nachteil: Jede Verifikation braucht einen Cache-Lookup - der Stateless-Vorteil wird teilweise aufgehoben.

Short-lived Access + Long-lived Refresh Tokens ist der Best-Practice-Ansatz: Access Token läuft nach 15 Minuten ab, Refresh Token nach 7-30 Tagen. Logout invalidiert nur den Refresh Token in der Datenbank.

Do's und Don'ts: Eine ehrliche Checkliste

Richtig machen
  • RS256 statt HS256 in Microservice-Umgebungen
  • Access Token Ablaufzeit: 15-60 Minuten
  • Refresh Token in httpOnly-Cookie speichern
  • Nur das Minimum an Claims im Payload
  • Token-ID (jti) für Revokation-Fähigkeit setzen
  • HTTPS für alle Endpoints erzwingen
  • JWKS-Endpoint für Public-Key-Verteilung
  • Aussteller (iss) und Audience (aud) validieren
Vermeiden
  • Sensitive Daten im Payload speichern
  • Token-Lebensdauer > 24 Stunden ohne Refresh
  • Access Token in localStorage (XSS-Risiko)
  • Signatur-Algorithmus nicht validieren (alg: none-Angriff)
  • Private Keys in Repositories oder Images
  • JWT ohne Ablaufzeit (exp)
  • HS256 mit demselben Secret über alle Services
Bekannte Angriffsvektoren

Der Algorithm Confusion Attack ist eine reale Schwachstelle: Wenn ein Service den Algorithmus aus dem JWT-Header akzeptiert ohne ihn zu validieren, kann ein Angreifer alg: none setzen und die Signaturprüfung umgehen. Spring Security konfiguriert den erwarteten Algorithmus serverseitig - der Header-Wert wird ignoriert. Eigene Implementierungen müssen das explizit absichern.

Allein in der ersten Jahreshälfte 2025 wurden sechs kritische CVEs in verbreiteten JWT-Bibliotheken und Cloud-Plattformen veröffentlicht, die von Privilege Escalation bis zu Denial-of-Service-Angriffen durch manipulierte aud-Claims reichen.

Der aud-Claim: in Microservices oft vergessen

In verteilten Architekturen gibt es einen weiteren, häufig übersehenen Angriffsvektor: den Cross-Service Relay Attack. Wenn ein Token keinen aud-Claim (Audience) enthält oder dieser nicht validiert wird, kann ein gültiger Token, der für Service A ausgestellt wurde, auch bei Service B akzeptiert werden. Ein Angreifer mit Zugriff auf einen Token kann damit lateral durch die Microservice-Landschaft navigieren - ohne neue Tokens fälschen zu müssen.

Die Lösung ist einfach: Jeder Service validiert, ob sein eigener Identifier im aud-Claim enthalten ist. In Spring Security geschieht das über die JWT-Decoder-Konfiguration - aber nur, wenn es explizit konfiguriert wird. Standardmäßig findet diese Prüfung nicht statt.

Fazit: JWT ist kein Allheilmittel

JWT löst das richtige Problem auf die richtige Art - aber nur, wenn man versteht, was es löst und was nicht. Stateless Authentifizierung ohne zentralen Session-Store ist für Microservices die korrekte Architektur. Die Schwachstellen - kein nativer Revokations-Mechanismus, Payload nicht verschlüsselt, Abhängigkeit von kurzlebigen Tokens - sind keine Fehler des Standards, sondern inherente Trade-offs, die durch bewusstes Design adressiert werden müssen.

Die wichtigsten Weichenstellungen: RS256 statt HS256, kurze Ablaufzeiten mit Refresh-Token-Rotation, kein Sensitive Data im Payload, und ein klares Konzept für Token-Revokation. Wer diese vier Punkte richtig umsetzt, hat eine solide, skalierbare Auth-Schicht - ohne zusätzliche Infrastruktur.

Praktische Faustregel

Wenn du nicht weißt, wie du Token-Revokation implementierst, mach die Token kürzer. Eine Ablaufzeit von 15 Minuten löst 90 % der Sicherheitsprobleme, die entstehen, wenn ein Token in falsche Hände gerät - ohne zusätzliche Infrastruktur.


Security-Review für Ihre Microservice-Architektur?

In einem strukturierten Architektur-Review analysiere ich Ihren bestehenden Authentifizierungsansatz und identifiziere konkrete Schwachstellen - als schriftliches Ergebnis, in einem halben Tag für 690 € netto.

Zum Architektur-Review
  1. Vaadata, "JWT: Vulnerabilities, Attacks & Security Best Practices", April 2025 - Analyse verbreiteter JWT-Implementierungsfehler; ergänzt durch eigene Projektbeobachtungen.
  2. OWASP API Security Top 10, 2023 Edition, API2:2023 Broken Authentication - listet explizit unsichere JWT-Konfigurationen (fehlende Signaturprüfung, alg: none, fehlende Ablaufzeit) als eine der häufigsten API-Schwachstellen.
  3. PortSwigger Web Security Academy, "JWT attacks" - dokumentiert den Algorithm Confusion Attack und alg: none-Bypass als reproduzierbare Angriffsvektoren gegen fehlerhafte JWT-Implementierungen.
  4. Security Boulevard / SSOJet, "JWT Security in 2025: Critical Vulnerabilities Every B2B SaaS Company Must Know", Juni 2025 - Dokumentation von sechs kritischen CVEs (u.a. CVE-2025-4692, CVE-2025-30144, CVE-2025-27144) in JWT-Bibliotheken und Cloud-Plattformen.
  5. Traceable.ai, "JWTs Under the Microscope: How Attackers Exploit Authentication and Authorization Weaknesses", Dezember 2024 - Analyse des Cross-Service Relay Attacks und der Bedeutung des aud-Claims in Microservice-Architekturen.