Spring Boot - Microservice & Rest API mit JWT & Spring Security absichern

In diesem Tutorial zeige ich dir wie du mit Spring Boot, Spring Security & JWT dein Micrsoservice oder Rest API gegen Unbefugte absichern kannst.
spring boot security jwt tutorial

Spring Boot Security – Sichere Microservices & RESTful API’s entwicklen mit JWT

Einleitung

Jeder Programmierer oder Softwareentwickler, der einen Microserive entwickelt, stellt sich irgendwann die Frage: „Wie sichere ich mein Microserivce gegen Unbefugte?“ oder „Wie erreiche ich, dass meine Microservices nur von angemeldeten Usern genutzt werden darf?“. In diesem Tutorial werde ich euch zeigen, wie ihr eure Microservices oder Rest API gegen unbefugte Nutzung sperren könnt. Hierfür werden wir Spring Boot, Spring Security und JWT verwenden. Wir werden zwei Microservices erstellen, an denen ich euch zeigen werde, wie ihr einen JWT Token erstellt und wie ihr diesen JWT Token nutzen könnt, um eure Microservices und Rest APIs abzusichern.

Voraussetzung

Auf meinem Youtube Channel findet ihr mein Video Tutorial wie ihr einen kompletten Rest Controller erstellen könnt. Der Rest Controller ist die Voraussetzung für dieses Tutorial.

Ein Microservice oder RESTful API, der nicht abgesichert ist!

Als Microservice Beispiel haben wir einen InfoService, der einen InfoResponse Object zurück gibt, in dem die aktuelle Serverzeit enthalten ist. Hierfür haben wir einen RestController, der über die URL http://localhost:8080/info das InfoObject zurückgibt. Der Service ist absichtlich simpel gehalten, da es in dem Tutorial hauptsächlich darum geht, einen Microservice abzusichern. Wie man umfangreichere RestController erstellt, könnt ihr hier bei meinem Tutorial „Spring Boot – RestController Klassen erstellen“ nachlesen.

Folgender Abschnitt zeigt einen kompletten RestController, der über den GET Aufruf von http://localhost:8080/info das InfoObject zurück gibt.

package de.ertantoker.tutorial;

import de.ertantoker.tutorial.response.InfoResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/info")
public class InfoController {

    @GetMapping
    public ResponseEntity getCustomers() {
        InfoResponse infoResponse = new InfoResponse();
        infoResponse.setInfo("Demo Info");
        return new ResponseEntity<>(infoResponse, HttpStatus.OK);
    }
}

Mit dem Tool Postman können wir nun unseren Rest API aufrufen und sehen dann folgendes Ergebnis.

Was ist ein JWT Token?

Wir haben es jetzt geschafft, dass wir unsere Rest API konsumieren können. Das Problem hierbei ist aber, dass jeder diese API konsumieren kann, wenn der Microservice im Internet verfügbar wäre. Genau das wollen wir mit diesem Tutorial verhindern. Wir müssen es schaffen, dass unser Microservice bzw unsere Rest API erkennt, von wem die Anfrage kommt. Hierfür können wir JWT Token nutzen.

JWT steht für JSON Web Token. Im Grunde ist es wie ein Ticket bzw. Zugangskarte, welches der Konsumierer eines Microservices erhält, mit dem er sich beim Konsumieren eines Microservices ausweisen muss. Ein JWT Token wird stets serverseitig erstellt, signiert und dem Konsumenten zur Verfügung gestellt.

Wie das geht, werden wir weiter unten erfahren. Wichtig bei der Nutzung von einem JWT Token ist es, dass der Token bei jeder Anfrage an das Microservice mit versendet wird. Der Microservice, in unserem Fall unser InfoService, validiert dann den JWT Token und entscheidet, ob er die HTTP Anfrage zulässt oder nicht. Man kann das Ganze mit der Zugangskarte vergleichen. Natürlich ist eine Zugangskarte nicht für die Ewigkeit gültig. Spätestens, wenn man das Unternehmen verlässt, wird einem Mitarbeiter die Zugangsakarte entzogen. Dies gilt auch für unseren JWT Token. Unser Token wird einen „Ablaufdatum“ erhalten. Dieses Datum definiert, wann unser JWT Token nicht mehr gültig ist. Wichtig hier ist es, dass abgelaufene Token vom InfoService mit dem HTTP Status 401 beantwortet werden.

Wie ein JWT Token aufgebaut ist, könnt ihr auf der folgenden Wikipedia Seite nachschauen.

AuthenticationService

Der AuthenticationService – Die Grundlage für die Sicherheit unserer Microservices

Nun wissen wir, dass wir einen JWT Token brauchen. Die Frage ist nun: „Wie erzeugen wir uns einen JWT Token?“, den wir dem Client senden können?

Hierfür werden wir einen neuen Authentication Microservice erstellen. Dieser hat die Aufgabe, über einen HTTP Post Aufruf für den Client einen JWT Token zu erstellen. Die URL für den Aufruf des AuthenticationService sieht dann wie folgt aus: http://localhost:8888/login. Wie ihr sehen könnt, wird dieser Microservice mit dem Port 8888 gestartet. Würde man hier den Default Port (8080) von Spring Boot verwenden, dann würde der Microservice, den man als zweites starten möchte, nicht gestartet werden, weil der Port schon belegt ist.

Wie oben erwähnt, werden wir einen HTTP Post Aufruf an unseren AuthenticationService senden. Der Grund, wieso hier explizit ein POST Aufruf gesendet wird und nicht ein GET ist der, dass GET Anfragen von vielen HTTP Servern, wie Apache Webserver oder nginx in der access.log geloggt werden. Somit würde man das Passwort des Users der einen JWT Token haben möchte, in die Access Logs schreiben, welches ein unnötiges Sicherheitsrisiko ist.

Folgender Code Abschnitt zeigt den kompletten RestController für den AuthenticationService:

package de.ertantoker.tutorial.controller;

import de.ertantoker.tutorial.exception.EntityNotFoundException;
import de.ertantoker.tutorial.request.AuthenticationRequest;
import de.ertantoker.tutorial.response.JWTTokenResponse;
import de.ertantoker.tutorial.service.AuthenticationService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping
public class AuthenticationController {

    private AuthenticationService authenticationService;

    public AuthenticationController(AuthenticationService authenticationService) {
        this.authenticationService = authenticationService;
    }

    @PostMapping("/login")
    public ResponseEntity createCustomer(@RequestBody AuthenticationRequest request) {
        return new ResponseEntity<>(authenticationService.generateJWTToken(request.getUsername(), request.getPassword()), HttpStatus.OK);
    }

    @ExceptionHandler(EntityNotFoundException.class)
    public ResponseEntity handleEntityNotFoundException(EntityNotFoundException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
    }
}

Mit einem RestController ist unser AuthenticationService noch nicht fertig implementiert. Wie ihr an dem Code Beispiel sehen könnt, wird die Anfrage an die Methode getJWTToken() der Klasse AuthenticationService.java weiter geleitet. In dieser Methode wird unser JWT Token erstellt und signiert.

Folgender Code Abschnitt zeigt den kompletten AuthenticationService Code

package de.ertantoker.tutorial.service;

import de.ertantoker.tutorial.exception.EntityNotFoundException;
import de.ertantoker.tutorial.repository.AccountRepository;
import de.ertantoker.tutorial.response.JWTTokenResponse;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class AuthenticationService {

    private AccountRepository accountRepository;
    private JwtTokenService jwtTokenService;
    private PasswordEncoder passwordEncoder;

    public AuthenticationService(AccountRepository accountRepository, JwtTokenService jwtTokenService, PasswordEncoder passwordEncoder) {
        this.accountRepository = accountRepository;
        this.jwtTokenService = jwtTokenService;
        this.passwordEncoder = passwordEncoder;
    }

    public JWTTokenResponse generateJWTToken(String username, String password) {
        return accountRepository.findOneByUsername(username)
                .filter(account ->  passwordEncoder.matches(password, account.getPassword()))
                .map(account -> new JWTTokenResponse(jwtTokenService.generateToken(username)))
                .orElseThrow(() ->  new EntityNotFoundException("Account not found"));
    }
}

An der Implementrierung der Methode erkennen wir, dass zuerst über das AccountRepository genutzt wird um ein Account Objekt in der Datenbank zu suchen. Wie Spring Boot Repository Interfaces funktionieren, könnt ihr bei meinem Tutorial „Spring Boot Repository“ nachlesen.

Die Methode findOneByUsername gibt als Rückgabewert ein Optional Object zurück, welches entweder leer sein kann oder ein Account Objekt beinhalten kann. Auf das Optional Objekt können wir jetzt die Filter Methode aufrufen. Hier prüfen wir nun, ob das verschlüsselte Passwort auch das ist, welches übertragen wurde. Sollte die Filter Methode ein true zurück liefern, dann können wir das Ergebnis an den JwtTokenService weitergeben, der uns den JWTTokenResponse erzeugt.

Beachtet, dass die map Methode hier nur aufgerufen wird, wenn die Filter Methode ein true zurück geliefert hat. Sollte die Filter Methode ein false liefern, also die Passwörter nicht identisch sind, dann wird eine EntityNotFoundException geworfen, welches beim übergeordneten AuthenticationController über den ExceptionHandler abgefangen wird. Die orElseThrow Methode wird auch aufgerufen, wenn das Optional Objekt ein empty beinhaltet. Also kein Account Objekt zu dem Usernamen gefunden werden konnte.

Nachdem wir uns die Implementierung in der AuthenticationService Klasse angeschaut haben, schauen wir uns nun die Klasse JwtTokenService.java an. Hier konzentrieren wir uns auf die Methode String generateToken(String username), welches uns den Token erstellt. Ich werde hier nicht detailliert auf die Implementierung eingehen. Die Implementierung ist selbsterklärend.

Folgender Code Abschnitt zeigt den kompletten JwtTokenService Code:

package de.ertantoker.tutorial.service;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;

@Component
public class JwtTokenService {

    private String secret;

    private Long expiration;

    public JwtTokenService(@Value("${jwt.secret}") String secret,
                           @Value("${jwt.expiration}") Long expiration) {
        this.secret = secret;
        this.expiration = expiration;
    }

    public String generateToken(String username) {
        final Date createdDate = new Date();
        final Date expirationDate = calculateExpirationDate(createdDate);

        return Jwts.builder()
                .setClaims(new HashMap<>())
                .setSubject(username)
                .setIssuedAt(createdDate)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    private Date calculateExpirationDate(Date createdDate) {
        return new Date(createdDate.getTime() + expiration * 10000);
    }
}

AuthenticationService testen

Nun sind wir mit der Implementierung des AuthenticationServices fertig! Jetzt werden wir unser Rest Interface testen ob dieser genau das macht, was wir implementiert haben.

Bevor ihr die Spring Boot Applikation startet, solltet ihr wissen, dass ihr eine laufende mongoDb Installation oder einen MongoDB Container auf Port 27017 benötigt. Die Spring Boot Applikation ist so implementiert, dass beim Start der Anwendung ein Demo Account mit den Werten Username: DemoAccount und Passwort: DemoPassword angelegt wird. Die Implementierung hierfür findet ihr in der Klasse ApplicationStartup.java.

Nachdem wir nun unsere AuthenticationService Microservice gestartet haben, senden mir mit Postman einen einen HTTP Post Aufruf auf die URL: http://localhost:8888/login.

Als BodyRequest senden wir folgende JSON Daten:

{
	"username": "DemoAccount",
	"password": "DemoPassword"
}

An dem Bild open erkennen wir nun, dass wir einen JWT Token erhalten haben. Euren Token könnt ihr nun auf der Seite https://jwt.io/ prüfen, ob dieser gültig ist. Achtet hier bitte darauf, dass ihr euren secret braucht, um zu prüfen, ob die Signatur des Tokens stimmt. Euren secret, welches in der application.yml hinterlegt ist solltet ihr Niemandem geben!

Wir sollten auch testen, ob unser AuthenticationService auch funktioniert, wenn wir einen Username eingeben, welches nicht in der MongoDB enthalten ist. Hierfür senden wir eine erneute Anfrage und verändern den Usernamen in der HTTP Post Anfrage. Nun sollten wir ein HTTP Status 404 als Response Code bekommen. Zusätzlich kann man auch testen, indem man das Passwort verändert. Auch in diesem Fall sollten wir einen HTTP Status 404 als Response Code erhalten. Diesen Test überlasse ich euch.

Der sichere InfoService

Den AuthenticationService haben wir nun abgeschlossen. Jetzt ist es an der Reihe, den InfoService mit Spring Security abzusichern. Spring Security ist ein eigenständiges Spring Projekt, welches dem Entwickler erlaubt, auf einfachste Art & Weise seine Spring Boot Anwendung abzusichern.

Die pom.xml Datei erweitern

Der erste Schritt ist die Erweiterung der pom.xml Datei. Hier müssen wir die dependency für Spring Security einbinden. Nach dem Einbinden ist Spring Security direkt aktiv. Das passiert nur, weil Spring Boot Security per default so konfiguriert ist, dass über die AutoConfiguration von Spring Boot die entsprechenden Konfigurationen geladen werden.

Folgender Code Abschnitt zeigt die komplette pom.xml Datei:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>de.ertantoker.tutorial</groupId>
  <artifactId>spring-boot-info-service</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>spring-boot-info-service</name>
  <url>http://maven.apache.org</url>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.4.RELEASE</version>
  </parent>

  <properties>
    <java.version>1.8</java.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt</artifactId>
      <version>0.9.1</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

Die Filter Klasse

Als nächstes brauchen wir einen ServletFilter, mit dem wir den JWT Token aus der HTTP Anfrage extrahieren können. In der Klasse JwtAuthenticationTokenFilter.java werden wir hierfür den JWT Token aus dem Request Object übernehmen und diesen an den SecurityContext von Spring Security übergeben. Die Übergabe an den SecurityContext sorgt dafür,  dass der Token von Spring Security ausgewertet wird.

Folgender Code Abschnitt zeigt die komplette JwtAuthenticationTokenFilter.java Datei

package de.ertantoker.tutorial.security;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Value("${jwt.header}")
    private String tokenHeader;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        final String requestHeader = request.getHeader(this.tokenHeader);

        if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
            String  authToken = requestHeader.substring(7);
            JwtAuthentication authentication = new JwtAuthentication(authToken);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        chain.doFilter(request, response);
    }
}

Die JwtAuthenticationEntryPoint Klasse

Als nächstes schauen wir uns die Klasse JwtAuthenticationEntryPoint.java an. Dieser dient dazu, um bei Anfragen, die unautorisiert sind, einen HTTP Status Code SC_UNAUTHORIZED zurück zu geben.

Folgender Code Abschnitt zeigt die komplette JwtAuthenticationEntryPoint.java Datei:

package de.ertantoker.tutorial.security;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
    }
}

Die JwtAuthenticationProvider Klasse

Die eigentliche Überprüfung findet dann in der Methode public Authentication authenticate(Authentication authentication) der JwtAuthenticationProvider.java Klasse. Hier wird der Token, den wir vorher in der JwtAuthenticationTokenFilter Klasse extrahiert haben, ausgewertet. Zusätzlich wird der Username aus dem Token extrahiert und, falls der Token valide ist, als JwtAuthenticatedProfile zurück gegeben.

Somit steht dann das JwtAuthenticatedProfile Objekt dem Spring SecurityContext zur Verfügung und kann zu einem späteren Zeitpunkt, an diversen Stellen im Code, ausgewertet werden, wenn man es will.

Sollte der Token nicht valide sein, dann wird eine JwtAuthenticationException geworfen. Unter Umständen kann es vorkommen, dass eine HTTP Anfrage gesendet wurde, wo zwar ein Bearer Token im Header enthalten ist, aber dieser Token kein echter JWT Token ist und somit nicht ausgewertet werden kann, dann wird ebenfalls eine JwtAuthenticationException geworfen.

Folgender Code Abschnitt zeigt die komplette JwtAuthenticationProvider.java Datei:

package de.ertantoker.tutorial.security;

import io.jsonwebtoken.JwtException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;

@Component
public class JwtAuthenticationProvider implements AuthenticationProvider {

    private static final Logger log = LoggerFactory.getLogger(JwtAuthenticationProvider.class);

    private final JwtTokenService jwtService;

    @SuppressWarnings("unused")
    public JwtAuthenticationProvider() {
        this(null);
    }

    @Autowired
    public JwtAuthenticationProvider(JwtTokenService jwtService) {
        this.jwtService = jwtService;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        try {
            String token = (String) authentication.getCredentials();
            String username = jwtService.getUsernameFromToken(token);

            return jwtService.validateToken(token)
                    .map(aBoolean -> new JwtAuthenticatedProfile(username))
                    .orElseThrow(() -> new JwtAuthenticationException("JWT Token validation failed"));

        } catch (JwtException ex) {
            log.error(String.format("Invalid JWT Token: %s", ex.getMessage()));
            throw new JwtAuthenticationException("Failed to verify token");
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return JwtAuthentication.class.equals(authentication);
    }
}

Die WebService Security Config Klasse

Zum Schluss muss nur noch die Spring Security Config an die eigene Anwendung angepasst werden. Hierfür schreiben wir eine eigene Config Klasse mit dem Namen WebSecurityConfig.java. In dieser Konfigurationsklasse werden alle Security bezogenen Konfigurationen vorgenommen.

In unserem Beispiel haben wir keine URL, die wir nicht absichern wollen. Jede URL, die von Spring Boot aus angeboten wird, wird abgesichert.

Folgender Code Abschnitt zeigt die komplette WebSecurityConfig.java Datei:

package de.ertantoker.tutorial.config;

import de.ertantoker.tutorial.security.JwtAuthenticationEntryPoint;
import de.ertantoker.tutorial.security.JwtAuthenticationProvider;
import de.ertantoker.tutorial.security.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


@SuppressWarnings("SpringJavaAutowiringInspection")
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtAuthenticationEntryPoint unauthorizedHandler;

    @Autowired
    private JwtAuthenticationProvider jwtAuthenticationProvider;

    @Autowired
    public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) {
        authenticationManagerBuilder.authenticationProvider(jwtAuthenticationProvider);
    }

    @Bean
    public JwtAuthenticationTokenFilter authenticationTokenFilterBean() {
        return new JwtAuthenticationTokenFilter();
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .csrf().disable()
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests()
                .anyRequest().authenticated();

        httpSecurity.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
        httpSecurity.headers().cacheControl();
    }
}

InfoService RESTful API mit Spring Security testen

Nachdem nun alles implementiert ist und die Spring Security Config durch die Annotation @EnableWebSecurity aktiviert wurde, starten wir unsere beiden Microservices neu. Wie ihr gesehen habt, haben wir nichts an der Implementierung des InfoController geändert.

Jetzt ist unsere Rest API gegen die Benutzung von Unbefugten gesichert. Wir sollten aber trotzdem noch testen, ob Anfragen, die ein gültiges Token haben, auch ordentlich beantwortet werden von unserem Microservice. Hierfür setzen wir unserer HTTP GET Anfrage für den Bearer Authorization Header einen gültigen JWT Tokens ein. Jetzt starten wir die Anfrage erneut und sehen, dass die Anfrage diesmal einen HTTP Status Code 200 und einen InfoResponse Objekt zurück gibt.

Das Ergebnis

Wir haben in diesem Tutorial gelernt, wie wir einen AuthenticationServer implementieren können, der uns einen JWT Token zurück gibt, den wir dann für weitere Anfragen gegen unseren Abgesicherten Services nutzen können. Dank Spring Security haben wir unsere MicroServices so abgesichert, dass diese Rest API nur noch mit einem gültigen JWT Token aufgerufen werden kann.

Das Prinzip mit dem JWT Token ist aber in dieser Form nicht wirklich 100% sicher! Solltet ihr nun eure Anfragen mittels HTTP senden und nicht über eine verschlüsselte Verbindung wie HTTPS, dann ist es für einen Angreifer sehr einfach an den JWT Token dran zu kommen. Ihr solltet eure Client Anwendung so implementieren, dass der JWT Token auch sicher abgelegt wird.

Der JWT Token ist wie ein Schlüssel eines Hauses. Ist der Schlüssel mal verloren oder geklaut worden, dann muss man einen neuen Schlüssel anfertigen lassen oder das Schloss auswechseln. Dies gilt auch für unseren JWT Token. Sollte der mal in fremde Hände geraten, dann ist es zwingend erforderlich, dass der secret (application.yml) bei beiden Microservices ausgetauscht wird.

Eine andere Möglichkeit ist, das ExpireDate des JWT Token so gering wie möglich zu halten. Dies würde dafür sorgen, das das entwendete JWT Token in einer sehr kurzen Zeit ungültig werden.

Den kompletten Quellcode zu dem Tutorial findet ihr auf meiner GitLab Seite

Ich hoffe euch hat mein Tutorial gefallen. Solltet ihr Fragen, Kritik oder Anregungen haben, dann schreibt ein Kommentar weiter unten.

Verwandte Beiträge

Comments (20)

Dein Tutorial ist unvollständig,
z.B fehlt die Methode getUsernameFromToken sowohl beim Git Repository, als auch hier im Tutorial.

Ok, ich habe die folgenden Java Klassen und Methoden im spring-boot-info-service bei dir im Repo gefunden. 🙂

Freut mich das du es noch gefunden hast. Hatte mich schon gewundert wieso das fehlt.

es gibt zwei Klassen die den gleichen Namen haben aber in unterschiedlichen Packeten liegen, aber warum ? gibt es eine die Klasse zweimal ?

Hallo Mario, vielleicht sehe ich das gerade nicht. Welche Klassen sind das?

Im Git Repo gibt es zwei Packete ( spring-boot-security-jwt-example
, und Spring boot Info Service) und da gibt es gleiche Klassen mit zum Teil unterschiedlichen Inhalt, was verwirrend ist

Mario wenn du mir sagst wie die Klassennamen sind, dann kann ich dir gerne darauf eine konkrete Antwort geben.

JwtTokenService.java sind in Info Service und in authentication-service vorhanden.

Hi Mario, ja das stimmt die Klasse gibt es zwei mal. Hintergrund hier ist, dass einmal das AuthenticationService Projekt die Klasse benötigt und JWTTokenService ebenfalls. In einem echten Projekt hätte man das auch in eine Bibliothek auslagern können und dann per Maven als Dependency einbinden können.

Wichtig ist nur zu wissen, dass der AuthenticationService die Klasse benötigt um den Token auszustellen und der InfoService um den Token zu validieren.

Toll! Genau das, wonach ich gesucht habe! 🙂

Hi. Erstmal danke für das Beispiel. Genau das was ich gesucht habe.
Wie könnte man denn implementieren, dass zum Beispiel bei /info nur Informationen für einen spezifischen Nutzer zurückgegeben werden? Also das der Username aus dem Token gelesen wird und dann nur Daten für diesen Nutzer rauskommen. Danke im Voraus

Großartiges Tutorial! Vielen Dank dafür.
Eine Frage:
Was spricht dagegen,
Infoservice und Authentifizierungsservice in eine Applikaton zu packen?
Also das ich entsprechend zwei Endpoints habe, /login und /info?

Vielen Dank!

Es spricht nichts dagegen. Ich wollte durch die Trennung aufzeigen wie man Microservices realisiert und diese dann sichert.
In dem Fall das du einen Monolithen bauen willst ist das vollkommen in Ordnung.

Hallo, wenn man mit JWT arbeitet und z.b ein Admin und eine Kunden Entity hat brauch, man doch keine Spring Security Rollen oder mehr ?

Hallo Hans, kommt drauf an was du mit dem Token machen willst. Wenn du auf der Client Seite rollen basiert Informationen ein und ausblenden willst, dann musst du die Information ebenfalls in den Token speichern. Wenn du im Backend die @Preauthorize Annotation von Spring nutzt um Rollen basiert die Ausführung einer Rest Methode zu erlauben oder zu verhindern, dann brauchst du die Rollen ebenfalls in Token.

Ich würde sagen du brauchst die Rollen.

Hallo, tolles Tutorial,

hätte eine Frage, wie kann ich den Token auf Seiten des Controller eigentlich auslesen an die Userinformation zu kommen um z.b an den Namen zu kommen um ihn in der DB zu finden ?

🙂

Hi Timm, da gibt es mehrere Möglichkeiten.

1. Du kannst den Authorization Header auslesen im Controller. Diesen kannst du sogar mit Spring Boot direkt injecten.
2. Die Andere Möglichkeit ist es den SecurityContextHolder von Spring zu nutzen. https://docs.spring.io/spring-security/site/docs/3.0.x/reference/technical-overview.html
Hier kannst du dann mit der Methode SecurityContextHolder.getContext().getAuthentication().getPrincipal() auf das Security Object zugreifen. Das war in meinem Fall der Token. Diesen kannst du dann über den JWTService nutzen so das du an die Werte in dem Token dran kannst. In den neuen Spring Boot Versionen kannst du soweit ich weis den SecurityContextHolder auch injecten.

Ich werde demnächst ein Video Tutorial über Spring Security und JWT machen. Kannst gerne meinen Youtube Kanal abonnieren wenn du magst. Da findest du Aktuell auch andere Spring Boot Tutorials.

https://www.youtube.com/channel/UC8JXDkTyT2i6Gddie6DJddg

Hallo,
für was ist den httpSecurity.headers().cacheControl(); in der Klasse JwtAuthenticationEntryPoint gut ?

Was genau macht Zeile 11 in der application.yml von dem spring-boot-authentication-service?
„spring.se security:“

Zumindest auf GitLab sieht das irgendwie Fehl am Platz aus.

Danke im Voraus 🙂

Hi Todd, vielen Dank für den Hinweis. Da hat sich ein Fehler in die application.yml geschlichen. Die hat natürlich da nichts zu suchen. Kann entfernt werden.

Leave a comment