CodeGym /Kurse /Spring Security /Fehlerbehebung 401/ 403 und Kontext

Fehlerbehebung 401/ 403 und Kontext

Spring Security
Level 28, Lektion 3
Verfügbar

1. Diagnose: Statuscodes und Symptome

Wenn Spring Security „kaputtgeht“, bricht es fast nie schön. Normalerweise sieht es so aus: „gestern hat es noch funktioniert“, „heute 403“, „warum?“ – und dann beginnt ein hektisches Herumfummeln an den Hebeln. Um aufzuhören, „Rate mal“ zu spielen, beginnen wir mit einer einfachen Regel: Bevor Sie den Code anfassen, müssen Sie in der Lage sein, das Problem so zu benennen, dass es eine andere Person (oder Sie selbst in einer Woche) versteht.

Ich denke gerne in Form einer kleinen „Incident Card“ – nicht aus Bürokratie, sondern für das Gehirn. Sie sollte Dinge enthalten, ohne die die Fehlerbehebung zu Magie wird: welcher Endpoint, welche HTTP-Methode, welcher Client, welcher Status, und vor allem – welches Authentifizierungsmodell haben Sie erwartet? (Session? Basic? Bearer/JWT?). Wenn Sie das festhalten, hört die Hälfte der „Merkwürdigkeiten“ plötzlich auf, merkwürdig zu sein.

Hier ist ein Beispiel einer solchen Karte in Tabellenform:

Feld Beispiel
Endpoint GET /api/me
Client Postman
Erwartetes Modell Bearer token (JWT-Abzweig)
Was gesendet wurde Authorization fehlt
Tatsache 401

Sehen Sie? Hier ist die Diagnose fast fertig, noch bevor Sie die Logs lesen: Wir haben JWT erwartet, aber überhaupt keinen Token gesendet. Und das ist nicht „Spring Security macht wieder Ärger“, sondern korrektes Verhalten.

Ein nützlicher Tipp für Anfänger: Wenn Sie nicht innerhalb von 10 Sekunden beantworten können, „welche Auth-Quelle soll den SecurityContext bei dieser Anfrage füllen?“, dann diagnostizieren Sie noch nicht das Problem, sondern Ihre eigene Erschöpfung.

401, 403, 404: Statuscodes unterscheiden

Viele beginnen das Debugging mit dem Satz „es hat einen Zugriffsfehler zurückgegeben“. Das ist ungefähr so, als würde man zum Arzt sagen: „Mir geht es schlecht“. Ja, wir haben Mitgefühl, aber nach dieser Beschreibung kann man nur mit Umarmungen behandeln. Deshalb beginnen wir in der Sicherheitswelt mit der Unterscheidung der Statuscodes: 401 und 403 sind verschiedene Geschichten. Und manchmal taucht in der Sicherheits-Fehlerbehebung unerwartet auch 404 auf, und auch der kann etwas mit Sicherheit zu tun haben, nicht nur mit einem fehlenden Controller.

Um sich schnell zu orientieren, behalten Sie diese Tabelle im Kopf:

Code Auf Deutsch Wo man normalerweise nach der Ursache sucht
401 „Wer bist du? Ich erkenne dich nicht / du hast deine Identität nicht bestätigt“ Authentifizierung: Session/Basic/JWT/Login
403 „Ich erkenne dich, aber du darfst nicht“ Autorisierung: Rollen, Berechtigungen, Owner-Prüfung, CSRF
404 „Ich kenne diesen Endpoint nicht“ Manchmal wirklich 404, manchmal Chain/Matcher/securityMatcher

Eine wichtige Feinheit zu 404: Wenn Sie mit securityMatcher(...) und mehreren Chains spielen, können Sie versehentlich eine Situation herbeiführen, in der ein Endpoint, der normalerweise „vom Filter selbst bereitgestellt“ wird (z.B. /login im formLogin-Modell), überhaupt nicht existiert, weil die Anfrage mit keiner SecurityFilterChain matcht. Spring Security warnt direkt davor, dass die von Filtern bereitgestellten Endpoints davon abhängen, ob die Anfrage mit dem ausgewählten securityMatcher matcht.

Und nun die entscheidende Wende für die Diagnose: Solange Sie sich nicht beantwortet haben, ob es sich um 401, 403 oder 404 handelt, haben Sie noch nicht mit der Fehlerbehebung begonnen – Sie sind nur traurig vor dem Bildschirm.

Nach den Konfigurationen, Proxy-Einstellungen und dem Härten von Oberflächen treten Probleme genau in dieser Form auf: 401, 403, leerer Kontext oder das Gefühl, dass „der Filter überhaupt nicht angesprochen hat“. Deshalb werden wir als Nächstes den Diagnosepfad durchgehen – von der erwarteten Authentifizierungsquelle bis zu dem Punkt in der Chain, an dem die Entscheidung tatsächlich getroffen wurde.

2. SecurityContext: Quelle und leere Zustände

Das Wort SecurityContext klingt beängstigend, aber im Wesentlichen ist es eine „Tasche im aktuellen Ausführungsthread“, in die Spring Security während der Bearbeitung einer Anfrage Informationen über den aktuellen Benutzer legt. Wenn in dieser Tasche die richtige Authentication liegt, kann Ihr Code (Controller, Services, Method Security) verstehen, „wer die Anfrage stellt“ und „welche Berechtigungen er hat“.

Wenn Sie „leerer SecurityContext“ hören, stellen Sie sich im Kopf gerne null vor. Aber in der Realität gibt es oft drei Zustände:

1) Kontext ist vorhanden, aber authentication == null (selten im Anwendungscode, eher in frühen Phasen),
2) Kontext ist mit AnonymousAuthenticationToken gefüllt (und das ist normal für öffentliche Anfragen),
3) Kontext ist mit Ihrer tatsächlichen Authentication gefüllt (username/password, basic, jwt usw.).

Wenn Sie schnell sehen möchten, was dort liegt, sieht der minimale diagnostische Punkt so aus:

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;

// Diagnose: prüfen, womit SecurityContext gerade gefüllt ist
Authentication auth = SecurityContextHolder.getContext().getAuthentication();

// In Logs/Konsole wird das für öffentliche Anfragen oft AnonymousAuthenticationToken sein
System.out.println(auth); // z.B.: AnonymousAuthenticationToken [...]

Ein paar Worte der Vorsicht. Das ist ein normales „Thermometer“, aber eine schlechte „Pille“. Das heißt, man kann nachschauen, aber „wenn es leer ist – lass mich selbst etwas hineinlegen“ – das ist der Anfang einer selbst geschriebenen Mini-Sicherheit, vor der wir im gesamten Kurs davongelaufen sind.

Und hier gibt es eine Nuance, die besonders Anfängern das Gehirn verknotet. Wenn ein Endpoint als permitAll markiert ist, kann Spring Security die Authentifizierung sogar aus der Session nicht holen, weil es für die Zugriffsgewährung keinen Sinn ergibt. In der Dokumentation wird das direkt beschrieben: Für permitAll und denyAll wird Authentication „nie aus der Session geholt“. Die praktische und unangenehme Konsequenz: Sie gehen auf einen öffentlichen Endpoint, erwarten „ich bin eingeloggt“ zu sehen, sehen aber anonymous – und ziehen die falsche Schlussfolgerung „die Session ist kaputt“. In Wirklichkeit schauen Sie einfach an der falschen Stelle. Es ist sinnvoller, den aktuellen Benutzer auf einem Endpoint zu diagnostizieren, der authenticated() oder eine Rolle/Berechtigung erfordert.

Um dies auf unser Projekt zu beziehen: Es ist besser, „wer bin ich“ auf /api/me zu prüfen, nicht auf /api/public/articles. Die öffentliche Zone ist per Definition nicht verpflichtet, Ihre Authentifizierung abzurufen und anzuzeigen.

Zur Veranschaulichung – ein kleines Diagramm, woher SecurityContext in unserem Kurs überhaupt kommen könnte:

flowchart TD
    %% Authentifizierungsquellen, die den SecurityContext füllen können
    R[HTTP request] --> FC[SecurityFilterChain]
    FC -->|session| S[SecurityContext aus HttpSession geholt]
    FC -->|Basic| B[Authorization: Basic ... gelesen]
    FC -->|JWT custom| J[Authorization: Bearer ... gelesen]
    FC -->|resource server| RS["JwtDecoder + built-in bearer pipeline"]
    FC --> A[Authentication in SecurityContext]
    A --> MVC["Controller/Service/@PreAuthorize"]

Und jetzt die Hauptfrage der Fehlerbehebung: Welcher Pfeil hätte in Ihrem Szenario feuern sollen?

3. SecurityFilterChain: Matcher und Reihenfolge

Manchmal sind Sie sich sicher: „Ich habe einen Filter hinzugefügt, er sollte aufgerufen werden“, aber tatsächlich verhält er sich wie eine Katze: „Ich bin nicht verpflichtet zu kommen, wenn Sie mich rufen.“ In Spring Security bedeutet das meistens nicht, dass der Filter „kaputt“ ist, sondern dass die Anfrage durch die falsche Chain gelaufen ist oder überhaupt nicht in die Security Chain gelangt ist, die Sie erwartet haben.

Der erste Grund ist die Verwechslung von securityMatcher(...) und requestMatchers(...). securityMatcher bestimmt, welche SecurityFilterChain überhaupt angewendet wird, während requestMatchers innerhalb der ausgewählten Chain die Autorisierungsregeln festlegen. Die Dokumentation betont, dass jeder requestMatchers(...) nur innerhalb des HttpSecurity funktioniert, dessen securityMatcher ebenfalls zutrifft.

Hierzu gehört auch eine weitere Überraschung: Wenn SecurityFilterChain nur auf /api/** matcht, Sie aber erwarten, dass sie den Endpoint /login (formLogin-Abzweig) „bereitstellt“, dann kann /login fehlen und wird 404 zurückgeben. Das kommt einem „sehr seltsam“ vor, bis Sie sich daran erinnern: Der Endpoint wird ebenfalls von Filtern bedient, und die Filter wurden nicht angewendet.

Der zweite Grund ist die Reihenfolge der Autorisierungsregeln. Das klassischste Anti-Beispiel sieht so aus:

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests(auth -> auth
            // WICHTIG: Diese breite Regel „frisst“ alle spezifischeren Regeln darunter
            .requestMatchers("/api/**").permitAll()     // hat alles unter /api „gefressen“
            // Diese Regel wird in der Matcher-Logik möglicherweise einfach nicht erreicht
            .requestMatchers("/api/admin/**").hasRole("ADMIN"));
    return http.build();
}

In diesem Fragment steht die Regel „alles unter /api/** erlauben“ vor „/api/admin/** schließen“. Das Ergebnis – der Admin-Bereich wird plötzlich öffentlich. Das ist kein Bug von Spring Security, sondern die Reihenfolge der Regeln. (Spring Security liest, wie ein Compiler, von oben nach unten, nicht „wie Sie es gemeint haben“.)

In der korrekten Variante schreiben Sie zuerst die spezifischen/sensitiven Regeln, dann die breiten:

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests(auth -> auth
            // Zuerst das Sensitivste schützen
            .requestMatchers("/api/admin/**").hasRole("ADMIN")
            // Dann die allgemeine Regel für die restliche API festlegen
            .requestMatchers("/api/**").authenticated());
    return http.build();
}

Der dritte Grund – mehrere Chains und @Order. Auf Fundamentals-Ebene haben wir versucht, Chains ohne Notwendigkeit nicht zu vermehren, aber selbst eine zusätzliche Chain kann die Situation schaffen: „Warum geht die Anfrage nicht dorthin?“ Im offiziellen Beispiel wird gezeigt, dass mehrere SecurityFilterChain per @Order und per securityMatcher ausgewählt werden und die Anfrage zuerst mit der höher priorisierten Chain matcht. Praktisches Symptom: Sie ändern eine Konfiguration, aber die Anfrage wendet eine andere an – und Sie denken, dass „die Einstellungen nicht übernommen wurden“.

Der vierte Grund – ein benutzerdefinierter JWT-Filter wurde hinzugefügt, aber nicht an der richtigen Position. Für benutzerdefinierte Filter wird meistens addFilterBefore(...) benötigt, damit Ihr JWT den SecurityContext vor der Autorisierung füllen kann:

import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

// jwtAuthenticationFilter – Ihr OncePerRequestFilter Bean
// WICHTIG: Filter vor UsernamePasswordAuthenticationFilter setzen, damit der Kontext gefüllt wird
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

Wenn der Filter zu spät steht, hat AuthorizationFilter bereits die Entscheidung „anonymous“ getroffen und Sie erhalten 401/403, noch bevor Ihr Code dran ist.

4. 401: Authentifizierungsfehler

401 – das ist die Geschichte, dass der Benutzer vom System nicht als „erkannt“ eingestuft wurde. Wichtig: „Nicht erkannt“ bedeutet nicht immer „existiert nicht“. Manchmal existiert er, aber Sie haben keine Credentials übermittelt, oder sie an die falsche Stelle gesendet, oder in einem Format, das Ihnen „richtig erscheint“.

Beginnen Sie mit der einfachen Frage: Welche Auth-Quelle erwarten Sie?

Wenn Sie ein session-basiertes Modell erwarten, dann ist Ihr Hauptzustandsträger das Cookie JSESSIONID (oder ein Äquivalent), und 401 bedeutet meistens, dass das Cookie fehlt, nicht das richtige ist oder die Session abgelaufen ist. Und hier tappt man leicht in die Postman/curl-Falle: Sie loggen sich einmal ein, speichern aber das Cookie nicht in der nächsten Anfrage – und die Anwendung sagt völlig zu Recht: „Ich kenne dich nicht.“ Da ist keine „Magie von Spring“, sondern reine HTTP-Realität.

Wenn Sie HTTP Basic erwarten, dann bedeutet 401 fast immer, dass der Authorization-Header fehlt oder nicht dem Format entspricht. Basic bedeutet „Credentials in jeder Anfrage“, und wenn sie fehlen, erscheint kein current user.

Wenn Sie JWT erwarten, prüfen Sie als Erstes den Authorization-Header. In einem benutzerdefinierten JWT-Filter gibt es oft eine Logik wie: „Wenn der Header fehlt – einfach ohne Authentifizierung weiterleiten“. Das ist kein Fehler, sondern eine normale Strategie: Der Filter sollte nicht bei jeder öffentlichen Anfrage abstürzen. Aber für einen geschützten Endpoint führt das folgerichtig zu 401.

Ein Minifragment, das man im Kopf haben sollte (und das man beim Debuggen lesen sollte), sieht so aus:

// Header holen, in dem JWT erwartet wird (Standardvereinbarung)
String header = request.getHeader("Authorization");

// Wenn der Header fehlt oder nicht mit dem Bearer-Präfix beginnt – Token als nicht vorhanden betrachten
if (header == null || !header.startsWith("Bearer ")) {
    // WICHTIG: Anfrage einfach ohne Authentifizierung weiterleiten (bleiben anonymous)
    filterChain.doFilter(request, response);
    return;
}

// Danach folgt normalerweise das Parsen/Prüfen des Tokens und das Füllen des SecurityContext

Aus Sicht der Fehlerbehebung bedeutet das: Das Fehlen des Präfixes Bearer ist fast gleichbedeutend mit dem Fehlen des Tokens. Nicht „funktioniert fast“, sondern „funktioniert überhaupt nicht“.

Und noch ein Punkt, den viele übersehen: Manchmal bedeutet „ich habe einen Token gesendet“ nicht „ich habe den Token dorthin gesendet, wo er gelesen wird“. Zum Beispiel haben Sie den Token in einen Query-Parameter ?token=... gelegt, aber der Filter sucht ihn nur im Header. Ergebnis: 401, SecurityContext leer, und Sie beginnen, eine Verschwörung zu vermuten.

Übrigens zum „leeren SecurityContext“. Wenn Sie 401 sehen und im Controller oder in den Logs SecurityContextHolder.getContext().getAuthentication() zeigt AnonymousAuthenticationToken, dann bedeutet das fast immer, dass die Authentifizierung einfach nicht stattgefunden hat. Nicht weil „der Benutzer keine Rolle hat“, sondern weil wir noch nicht einmal zu den Rollen gekommen sind.

5. 403: Autorisierungsfehler

403 – ist viel tückischer, weil es oft wie „falsche Rolle“ aussieht, obwohl die tatsächliche Ursache eine ganz andere sein kann. Bei 403 befinden wir uns bereits in der Welt, in der der Benutzer erkannt wurde (oder die Anfrage zumindest die Mechanismen durchlaufen hat, die eine Entscheidung getroffen haben), und nun sagt das System: „nicht erlaubt“.

Zunächst ist es hilfreich, drei häufige „Unterarten“ von 403 in unserem Projekt zu unterscheiden.

Die erste – Verbot auf Anforderungsebene. Das ist, wenn Ihr SecurityFilterChain gesagt hat: „in /api/admin/** nur ADMIN“, und Sie kommen als USER. Hier sind die Regeln von authorizeHttpRequests(...) schuld.

Die zweite – Verbot auf Methodenebene. Sie könnten den Zugriff auf URL-Ebene erlaubt haben, aber innerhalb der Service-Schicht hat Sie @PreAuthorize gestoppt. Von außen sieht es aus wie „ich bin doch in den Controller gekommen“, aber in Wirklichkeit hat eine andere Schicht die Entscheidung getroffen.

Die dritte – Verbot basierend auf dem Eigentümer. Dies ist eine besonders häufige Geschichte für die Secure Content Platform API: Sie haben die Rolle USER, sind authentifiziert, versuchen aber, den Entwurf eines anderen zu lesen oder das Profil eines anderen zu aktualisieren. Aus Sicherheitssicht ist das nicht „falsche Rolle“, sondern „nicht dein Objekt“. In Logs und Tests sollte dies ein separates Szenario sein.

Und nun die Hauptfalle: 403 kann überhaupt nichts mit Rollen/Eigentümer zu tun haben, sondern mit CSRF. Im session-basierten Abzweig endet eine statusändernde Anfrage ohne CSRF-Token oft mit 403. Und im Multipart-Upload-Szenario wird alles noch lustiger, weil die Datei der Body ist und der CSRF-Token auch irgendwo sein muss.

Spring Security diskutiert das Problem von multipart + CSRF gesondert als „Henne-Ei-Problem“: Um den CSRF-Token aus dem Body zu holen, muss man den Body lesen, also ist die Datei bereits auf dem Server angekommen. Daher wird für Browser-Anwendungen mit JavaScript empfohlen, den CSRF-Token im Header zu übermitteln – so zwingen Sie den Server nicht, den Body vor der CSRF-Prüfung zu lesen. In der praktischen Fehlerbehebung sieht das so aus: Sie versuchen ein POST auf /api/me/avatar, erhalten 403 und beginnen, die Rolle zu überprüfen, obwohl die richtige Frage wäre: „War überhaupt ein CSRF-Token vorhanden?“.

Noch eine Feinheit, die Erwartungen zerstört: Wenn Sie 403 im Admin/Editor-Bereich sehen, stellen Sie sicher, dass Sie Rolle und Authority in der richtigen Form vergleichen. hasRole("ADMIN") erwartet das Vorhandensein von ROLE_ADMIN, während hasAuthority("ADMIN") eine exakte Übereinstimmung der Zeichenfolge erwartet. Wenn Sie irgendwo die Namenskonventionen vermischt haben, wird 403 aussehen wie „gleiche Rolle, aber warum darf ich nicht?“. Das ist der Fall, wenn das Problem nicht in der Zugriffslogik liegt, sondern in den Zeichenfolgen (die ärgerlichste Art von Fehlern, weil sie wie Philosophie aussieht, aber in Wirklichkeit ein Tippfehler ist).

6. Spring Security Logs: Lesen ohne Tränen

Spring Security Logs erschrecken Anfänger oft genauso wie git rebase --interactive jemanden erschreckt, der nur „einen Commit umbenennen“ wollte. Aber die Logs haben eine gute Nachricht: Sie müssen nicht alles verstehen. Sie müssen aus ihnen Antworten auf zwei Fragen extrahieren: „Welche Chain hat gearbeitet?“ und „Welche Entscheidung wurde getroffen?“.

Der einfachste Weg, dies auf dem lokalen Rechner zu tun, ist, vorübergehend DEBUG-Level zu aktivieren:

logging:
  level:
    # DEBUG nur für die Dauer der Diagnose aktivieren: Es werden sehr viele Logs sein
    org.springframework.security: DEBUG

Danach sehen Sie, wie die Anfrage durch die Filter Chain läuft, welche Filter angewendet werden und wo genau die Entscheidung über 401/403 getroffen wird.

Es ist wichtig, dies vorsichtig zu verwenden. DEBUG ist wie ein Röntgenbild: nützlich, wenn ein Verdacht besteht, und schädlich, wenn Sie es jeden Morgen „zur Sicherheit“ machen. DEBUG dauerhaft eingeschaltet zu lassen bedeutet, im Rauschen zu ertrinken und das Signal nicht mehr zu sehen.

Nun zum „erwachsenen“ Lesen dieser Logs. Zuerst suchen Sie, ob die Anfrage überhaupt in eine Security-Kette gelangt ist. Dann suchen Sie, ob Authentication aufgetaucht ist (oder ob sie anonymous geblieben ist). Dann suchen Sie, wo die Entscheidung zur Verweigerung getroffen wurde. Wenn die Verweigerung AccessDenied ist, sind Sie im Bereich 403 und überprüfen Rollen/Berechtigungen/CSRF/Eigentümer-Prüfung. Wenn die Verweigerung auf fehlende Authentifizierung zurückgeht, sind Sie im Bereich 401 und überprüfen, warum die Auth-Quelle den Kontext nicht gefüllt hat.

Und ein kleiner psychologischer Sieg: Versuchen Sie nicht, „alle Filter zu lesen“. Sie sind nicht verpflichtet, den gesamten Katalog von Spring Security zu kennen. Sie müssen lernen, die Frage zu beantworten: „Wurde mein benutzerdefinierter JWT-Filter überhaupt aufgerufen?“ oder „Wurde die CSRF-Prüfung durchgeführt?“. Das reicht aus, damit 80% der Probleme aufhören, mysteriös zu sein.

7. Typische Fehler bei der Fehlerbehebung von Sicherheitsproblemen

Fehler Nr. 1: Jede Verweigerung als 403 betrachten und dann nach Rollen suchen.
Wenn ein Entwickler jede Verweigerung als „forbidden“ bezeichnet, macht er sich selbst die Diagnose kaputt. 401 ist eine fehlende Authentifizierung, und dabei spielen Rollen überhaupt keine Rolle. In der Folge überprüft die Person stundenlang hasRole(...), obwohl sie in Wirklichkeit den Authorization-Header nicht gesendet hat.

Fehler Nr. 2: Den SecurityContext auf einem permitAll-Endpoint diagnostizieren und den Schluss ziehen „es gibt keine Session“.
Das ist eine klassische Falle. Für permitAll kann Spring Security Authentication aus der Session nicht extrahieren. Die Dokumentation beschreibt dieses Verhalten direkt. Wenn Sie den Kontext an einem öffentlichen Endpunkt betrachten, können Sie anonymous sehen, obwohl Sie eingeloggt sind – und das ist kein Bug, sondern Optimierung/Semantik.

Fehler Nr. 3: „Der Filter hat nicht funktioniert“ → sofort den Filter umschreiben.
Meistens hat der Filter „nicht funktioniert“, nicht weil der Code des Filters schlecht ist, sondern weil die Anfrage nicht in die richtige SecurityFilterChain gelangt ist (Fehler im securityMatcher) oder der Filter an der falschen Position in der Reihenfolge steht. Beweisen Sie zuerst, dass die Chain und die Reihenfolge korrekt sind, dann ändern Sie den Code.

Fehler Nr. 4: Falsche Reihenfolge der Matcher, die versehentlich alles öffnet oder schließt.
Eine breite Regel wie /api/** „frisst“ leicht eine spezifischere Regel wie /api/admin/**, wenn sie weiter oben steht. Im Ergebnis öffnen Sie entweder versehentlich den Admin-Bereich oder schließen versehentlich alles. In beiden Fällen sieht das Symptom so aus, als ob sich Spring Security seltsam verhält, obwohl die Ursache reine Logik der Reihenfolge der Regeln ist.

Fehler Nr. 5: 403 aufgrund von CSRF mit 403 aufgrund von Rollen verwechseln.
Im session-basierten Abzweig sieht das Fehlen eines CSRF-Tokens bei POST/PATCH/DELETE oft aus wie „keine Rechte“, obwohl es tatsächlich „die Anfrage hat die CSRF-Prüfung nicht bestanden“ ist. Im Multipart-Upload-Szenario ist das noch schmerzhafter: Die Datei wird gesendet, und Sie suchen, wo die Rolle verloren gegangen ist. Spring Security beschreibt multipart+CSRF gesondert als besonderen Fall und empfiehlt die header-basierte Übertragung des Tokens, wenn ein JavaScript-Client vorhanden ist.

1
Aufgabe
Spring Security, Level 28, Lektion 3
Gesperrt
Diagnostischer Header für die ausgewählte SecurityFilterChain
Diagnostischer Header für die ausgewählte SecurityFilterChain
1
Aufgabe
Spring Security, Level 28, Lektion 3
Gesperrt
```
```
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION