44 Authentifizierung

Authentifizierung ist ein entscheidender Aspekt der Sicherheit in RESTful Webservices. Sie stellt sicher, dass nur autorisierte Benutzer oder Systeme auf geschützte Ressourcen zugreifen können. In Spring Boot gibt es mehrere Methoden, um die Authentifizierung zu implementieren, einschließlich Basic Authentication, OAuth2, JWT (JSON Web Token), und der Integration von Drittanbieterdiensten wie Spring Security.

In diesem Kapitel erklären wir, wie Sie die Authentifizierung in Spring Boot REST Services einrichten und implementieren können. Wir werden auf die gängigen Ansätze eingehen und Schritt-für-Schritt-Beispiele für die Implementierung von Basic Authentication und JWT-basierter Authentifizierung zeigen.

44.1 Überblick über die Authentifizierung

Die Authentifizierung in einer Webanwendung überprüft die Identität eines Benutzers oder Systems. Dies wird in RESTful Webservices oft durch Tokens oder HTTP-Header-basierte Authentifizierungen umgesetzt. Die gängigsten Ansätze für die Authentifizierung in RESTful Webservices sind:

  1. Basic Authentication: Benutzername und Passwort werden in einem HTTP-Header als Base64-codierter String übermittelt.
  2. OAuth2: Ein offenes Protokoll, das Token zur Authentifizierung verwendet.
  3. JWT (JSON Web Token): Ein offener Standard zur Übertragung von Informationen zwischen Parteien in einem kompakten, JSON-basierten Format, das signiert und optional verschlüsselt ist.

44.2 Basic Authentication in Spring Boot

Basic Authentication ist eine einfache Authentifizierungsmethode, bei der Benutzername und Passwort mit jeder Anfrage in einem HTTP-Header gesendet werden. Diese Methode ist einfach zu implementieren, jedoch sollte die Übertragung immer über HTTPS erfolgen, da die Anmeldedaten in Base64-codiertem Klartext übermittelt werden.

44.2.1 Schritt 1: Hinzufügen von Spring Security

Um Basic Authentication zu implementieren, verwenden wir Spring Security. Fügen Sie die folgende Abhängigkeit in Ihrer pom.xml hinzu:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

44.2.2 Schritt 2: Einfache Sicherheitskonfiguration

Erstellen Sie eine Konfigurationsklasse, die die Sicherheitsregeln für Ihre REST-API definiert. In diesem Beispiel wird Basic Authentication verwendet, und die Anfragen an die API-Endpunkte werden geschützt.

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests()
            .antMatchers("/products/**").authenticated() // Schützt den /products-Endpunkt
            .and()
            .httpBasic(); // Aktiviert Basic Authentication

        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails user = User
            .withUsername("admin")
            .password("password")
            .roles("ADMIN")
            .build();

        return new InMemoryUserDetailsManager(user); // Benutzer in In-Memory speichern
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance(); // Keine Verschlüsselung (nur zu Demo-Zwecken)
    }
}

44.2.3 Schritt 3: Testen von Basic Authentication

Die API-Endpunkte sind nun durch Basic Authentication geschützt. Um die geschützten Ressourcen zu testen, müssen Sie den HTTP-Header Authorization senden, der den Base64-codierten Benutzername und Passwort enthält.

44.2.3.1 Beispiel: Zugriff mit cURL

curl -u admin:password http://localhost:8080/products

In diesem Beispiel authentifizieren wir uns mit dem Benutzer admin und dem Passwort password. Spring Boot prüft die Anmeldedaten und gewährt den Zugriff auf den /products-Endpunkt, wenn die Authentifizierung erfolgreich ist.

44.3 JWT-basierte Authentifizierung

JSON Web Tokens (JWT) bieten eine sicherere und flexiblere Methode der Authentifizierung, bei der ein Token, das die Benutzeridentität bestätigt, nach der Authentifizierung an den Client zurückgegeben wird. Dieses Token wird bei jeder nachfolgenden Anfrage an den Server gesendet, und der Server überprüft die Gültigkeit des Tokens, anstatt jedes Mal Benutzername und Passwort zu überprüfen.

44.3.1 Schritt 1: JWT-Abhängigkeit hinzufügen

Fügen Sie die Abhängigkeit für jjwt (eine beliebte JWT-Bibliothek) in Ihre pom.xml ein:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

44.3.2 Schritt 2: Erstellen eines JWT-Utils

Erstellen Sie eine Utility-Klasse, um JWTs zu generieren und zu validieren.

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

@Component
public class JwtUtil {

    private String SECRET_KEY = "secret";

    public String extractUsername(String token) {
        return extractClaim(token, Claims::getSubject);
    }

    public Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration);
    }

    public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = extractAllClaims(token);
        return claimsResolver.apply(claims);
    }

    private Claims extractAllClaims(String token) {
        return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
    }

    private Boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }

    public String generateToken(String username) {
        Map<String, Object> claims = new HashMap<>();
        return createToken(claims, username);
    }

    private String createToken(Map<String, Object> claims, String subject) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // Token für 10 Stunden gültig
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
                .compact();
    }

    public Boolean validateToken(String token, String username) {
        final String extractedUsername = extractUsername(token);
        return (extractedUsername.equals(username) && !isTokenExpired(token));
    }
}

44.3.3 Schritt 3: JWT in der Authentifizierung verwenden

Erstellen Sie nun einen Authentifizierungs-Controller, um das JWT-Token zu generieren, wenn der Benutzer sich anmeldet.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.bind.annotation.*;

@RestController
public class AuthenticationController {

    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserDetailsService userDetailsService;

    @PostMapping("/authenticate")
    public String generateToken(@RequestBody AuthenticationRequest authRequest) throws Exception {
        try {
            authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword())
            );
        } catch (Exception ex) {
            throw new Exception("Invalid username or password");
        }

        final UserDetails userDetails = userDetailsService.loadUserByUsername(authRequest.getUsername());
        return jwtUtil.generateToken(userDetails.getUsername());
    }
}

44.3.4 Schritt 4: JWT im Header verwenden

Nachdem das Token generiert wurde, senden Sie es bei jeder weiteren Anfrage im HTTP-Header Authorization.

44.3.4.1 Beispiel: Zugriff mit JWT

curl -H "Authorization: Bearer <JWT-Token>" http://localhost:8080/products

In diesem Fall wird das JWT im Header gesendet, und der Server validiert das Token, bevor der Zugriff auf geschützte Ressourcen gewährt wird.

44.4 tl;dr

Die Authentifizierung in Spring Boot REST Services kann auf verschiedene Weise implementiert werden, abhängig von den Sicherheitsanforderungen Ihrer Anwendung. Während Basic Authentication einfach und leicht zu implementieren ist, bietet JWT-basierte Authentifizierung mehr Sicherheit und Flexibilität, insbesondere in Microservices-Architekturen oder modernen Webanwendungen. Durch die Kombination von Spring Security und JWT können Sie robuste und sichere RESTful Webservices mit minimalem Aufwand erstellen.