Lab 07

Security mit OAuth2/OIDC

Von vertrauten Headern zu geprüften JWT-Scopes und Method Security.

OAuth2OIDCJWTScopes
SchwierigkeitMittel+
Dauer75–105 Min
LernstufeStufe 6 – Praxislabor und Capstone
Arbeitsweise: Erst den Vorher-Code öffnen, dann Analyse lesen, anschließend die Nachher-Lösung und den Test vergleichen. Alle Abschnitte sind standardmäßig geschlossen.

Workshop-Slice

Dieses Lab zeigt einen konkreten Enterprise-Fehler mit Vorher/Nachher-Code. Die Beispiele sind bewusst fachlich benannt, damit du Architekturentscheidungen und nicht nur Syntax übst.

Lernziel

Du schützt einen Enterprise-Endpunkt mit geprüfter Identität, Rollen/Scopes und testbarer Autorisierung.

Ausgangslage

Ein interner Service vertraut auf Header wie X-User-Id und X-Role. In einer echten Plattform kann das bei falscher Gateway-Konfiguration zu Privilege Escalation führen.

Vorher: problematischer Code

Die API glaubt Headern, die jeder Client theoretisch senden kann.

UnsafeOrderController.javaJAVA
@PostMapping("/orders/{id}/cancel")
void cancel(@PathVariable String id,
            @RequestHeader("X-User-Id") String userId,
            @RequestHeader("X-Role") String role) {
    if (!"ADMIN".equals(role)) {
        throw new ResponseStatusException(HttpStatus.FORBIDDEN);
    }
    orderService.cancel(id, userId);
}
Analyse: Was ist daran schlecht?
  • Header sind kein Beweis für Identität.
  • Autorisierung ist im Controller hart codiert.
  • Scopes/Rollen sind nicht zentral dokumentiert.
  • Tests prüfen oft nur Happy Path.
Nachher: bessere Lösung

Die Zielversion nutzt JWT-Authentifizierung, Scope-Prüfung und Method Security am Use Case.

SecurityConfig.javaJAVA
@Configuration
@EnableMethodSecurity
class SecurityConfig {
    @Bean
    SecurityFilterChain api(HttpSecurity http) throws Exception {
        return http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers(HttpMethod.POST, "/orders/*/cancel").authenticated()
                .anyRequest().denyAll())
            .oauth2ResourceServer(oauth -> oauth.jwt(Customizer.withDefaults()))
            .build();
    }
}
CancelOrderUseCase.javaJAVA
@Service
class CancelOrderUseCase {
    // Pattern: Policy Enforcement at Application Boundary
    // Zweck: Fachliche Operation ist unabhängig vom Controller geschützt.
    @PreAuthorize("hasAuthority('SCOPE_order.cancel')")
    public void cancel(OrderId id, UserId requestedBy) {
        Order order = orders.get(id);
        order.cancel(requestedBy);
    }
}
scope-contract.yamlYAML
api: order-service
scopes:
  order.read: Bestellungen lesen
  order.create: Bestellungen anlegen
  order.cancel: Bestellungen stornieren
roles:
  support-agent: [order.read]
  order-manager: [order.read, order.create, order.cancel]
Test / Prüfnachweis

Dieser Abschnitt zeigt, wie du die Verbesserung nachweist. Es ist bewusst kein reiner Happy-Path-Test, sondern prüft ein Risiko aus dem Vorher-Teil.

OrderSecurityTest.javaJAVA
@Test
void cancelRequiresCancelScope() throws Exception {
    mvc.perform(post("/orders/O-55/cancel")
            .with(jwt().authorities(new SimpleGrantedAuthority("SCOPE_order.read"))))
       .andExpect(status().isForbidden());

    mvc.perform(post("/orders/O-55/cancel")
            .with(jwt().authorities(new SimpleGrantedAuthority("SCOPE_order.cancel"))))
       .andExpect(status().isNoContent());
}
Typische Fehler
  • Gateway-Header ungeprüft als Identität verwenden.
  • Rollen in jedem Controller anders benennen.
  • Nur UI verstecken, API aber nicht schützen.
  • 403/401 nicht unterscheiden.
Deep-Learning-Bezug

Die Links führen zum ausführlichen Inhalt; die Deep-Learning-Seite bleibt nur die Lernlandkarte und kopiert den Inhalt nicht doppelt.

Prüfcheckliste
  • Keine Autorisierung über ungeprüfte Client-Header.
  • Scope-Vertrag ist dokumentiert.
  • Negativtest für fehlende Berechtigung vorhanden.
  • Use Case ist zusätzlich geschützt.
⌂ Cockpit