1. @PreAuthorize ohne Methodensicherheit
Wenn man die Regel nicht mehr auf /api/editor/**, sondern direkt auf publish(...) setzen möchte, greift man fast automatisch zu @PreAuthorize.
Es gibt eine fast universelle Geschichte: Ein Student findet die Annotation @PreAuthorize, setzt sie auf eine Servicemethode … und nichts passiert. Die Methode wird aufgerufen, Daten ändern sich, die Veröffentlichung erfolgt, die Administration ist „versehentlich“ für die falsche Person zugänglich – und es entsteht der Eindruck, dass Spring Security Ihre Annotationen „nicht respektiert“. In Wirklichkeit respektiert es sie, es liest sie nur noch nicht.
In Spring Security gibt es zwei große Bereiche. Der erste ist die Web-Sicherheit: Filter, SecurityFilterChain, AuthenticationEntryPoint, AccessDeniedHandler, die ganze Pracht, die vor dem Eintreffen der Anfrage im Controller ausgelöst wird. Der zweite Bereich ist die Methodensicherheit: Zugriffsprüfung auf der Ebene des Methodenaufrufs eines Spring-Beans. Und der zweite Bereich wird standardmäßig nicht allein dadurch aktiviert, dass Sie spring-boot-starter-security hinzugefügt haben.
Um es ganz einfach auszudrücken: @PreAuthorize ist eine „Regel“, aber Regeln führen sich nicht selbst aus. Es braucht einen Mechanismus, der vor Ausführung der Methode sagt: „Stop, prüfen wir, ob hier Zutritt erlaubt ist.“ Dieser Mechanismus wird mit der Annotation @EnableMethodSecurity aktiviert.
Eine kleine Demonstration „wie man sich selbst täuschen kann“ sieht etwa so aus: Sie schreiben einen Service …
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
@Service
public class DemoService {
// Diese Annotation beginnt erst dann wirklich zu funktionieren, wenn die Methodensicherheit über @EnableMethodSecurity aktiviert wurde
@PreAuthorize("denyAll()")
public void doSensitiveStuff() {
// Wichtig: Wenn die Methodensicherheit nicht aktiviert ist, wird diese Methode trotzdem ausgeführt,
// weil Spring den Aufruf nicht abfängt und die Zugriffsregel nicht überprüft.
}
}
… und wundern sich dann, dass alles „funktioniert“. Ja, es funktioniert – denn @PreAuthorize bleibt ohne @EnableMethodSecurity in den meisten Fällen nur eine dekorative Beschriftung. Und was besonders unangenehm ist: Die Anwendung kann sich beschweren und warnen. Sie tut einfach so, als ob die Annotation nicht existiert.
2. Zwei Ebenen: Filterkette und Methodensicherheit
Um die Sicherheit nicht zu einem Brei werden zu lassen, ist es nützlich, die beiden Grenzen gedanklich zu trennen. Die erste Grenze ist der „Eingang zur Anwendung über HTTP“. Die zweite ist der „Eingang zur Geschäftsoperation“. Und ja, manchmal fallen diese Grenzen zusammen (ein Endpunkt → eine Servicemethode), aber oft gehen sie auseinander, besonders wenn es um die Wiederverwendung von Services, verschiedene Controller, Batch-Operationen, interne Aufrufe und andere „Freuden der realen Welt“ geht.
Das Schema, das man sich vor Augen halten sollte, ist dieses:
flowchart TD
C[Client] -->|HTTP request| F[SecurityFilterChain]
F -->|if passed| Ctrl[Controller]
Ctrl -->|method call| P[Service Proxy]
P -->|if passed| S[Service method]
Die Filter in SecurityFilterChain entscheiden, ob die Anfrage überhaupt weitergeleitet werden darf: ob anonyme Benutzer in den öffentlichen Bereich gelassen werden, ob eine Authentifizierung erforderlich ist, welche Rolle für /api/admin/** geeignet ist, und so weiter.
Die Methodensicherheit wird später aktiviert: wenn der Controller bereits den Service aufgerufen hat. Hier erfolgt die Prüfung vor der Ausführung der Methode (z. B. publish(...)), und zwar nicht anhand der URL, sondern anhand der aktuellen Authentication im SecurityContext und der Regeln auf der Methode.
Normalerweise ist es praktisch, den Unterschied in einer Tabelle festzuhalten, um Verwirrung zu vermeiden:
| Frage | Request-Ebene (über SecurityFilterChain) | Methodenebene (über @PreAuthorize) |
|---|---|---|
| Worauf schauen wir | URL, HTTP-Methode, Header, Matcher | Auf den Methodenaufruf und den aktuellen Benutzer |
| Wo lebt es | In der Security-Konfiguration () |
Auf der Servicemethode (und etwas in der Konfiguration über @EnableMethodSecurity) |
| Wann wird es ausgelöst | Vor dem Controller | Vor der Methode des Spring-Beans |
| Typische Aufgabe | „Wer darf auf /api/admin/** zugreifen?“ | „Wer darf lockUser() ausführen?“ |
| Was passiert, wenn man es vergisst | Endpunkt versehentlich offen/geschlossen | Kritische Methode von unerwarteten Stellen aus zugänglich |
Und hier ist die entscheidende Schlussfolgerung: SecurityFilterChain haben wir bereits (wir haben sie früher erstellt), aber die Methodensicherheit muss explizit aktiviert werden, sonst führt niemand die Annotationen auf den Methoden aus.
3. Rolle von @EnableMethodSecurity
Wenn Sie @EnableMethodSecurity hinzufügen, beginnt Spring beim Start der Anwendung die Infrastruktur aufzubauen, die Methodenaufrufe abfangen und prüfen kann, ob darauf Security-Annotationen vorhanden sind (in unserem Kurs vor allem @PreAuthorize). Unter der Haube stecken viele Details, aber auf dieser Stufe ist es wichtig, die praktische Idee zu verstehen: Spring „legt“ die Zugriffsprüfung vor die Ausführung der Methode.
Wenn Sie Analogien mögen (und wer mag sie nicht, außer Leuten, die Regex beim ersten Versuch schreiben), dann ist @EnableMethodSecurity wie das Einschalten einer Alarmanlage im Haus. Solange die Alarmanlage ausgeschaltet ist, können Sie zehn Schilder „Vorsicht, Wachschutz“ an die Tür hängen, aber sie beißen nicht. Wenn die Alarmanlage eingeschaltet ist, führt jeder Versuch, „dort hineinzugehen, wo man nicht soll“, zu einer Reaktion des Systems.
Ein weiterer wichtiger Punkt: Der Starter-Security aktiviert die Web-Sicherheit „out of the box“, weil es sonst zu einfach wäre, die Anwendung versehentlich offen zu lassen. Die Methodensicherheit wird standardmäßig nicht aktiviert, weil sie Aufrufe von Methoden innerhalb der Anwendung betrifft, also ein tiefgreifender, querschnittlicher Teil wird. Sie aktiviert AOP/Proxy-Mechanik und ändert den Aufrufpfad innerhalb der Anwendung, daher muss diese Entscheidung bewusst getroffen werden.
In der Praxis läuft in unserem Kurs alles auf eine einzige Zeile in der Konfiguration hinaus – und das ist angenehm.
4. Aktivierung der Methodensicherheit im Projekt
Die gute Nachricht: Technisch wird die Methodensicherheit mit einer sehr kurzen Klasse aktiviert. Die schlechte Nachricht: Genau wegen dieser Kürze wird sie am häufigsten vergessen.
In unserem Projekt ist es am bequemsten, eine separate Konfigurationsklasse im Paket com.example.securecontent.security.config anzulegen, damit sie neben SecurityFilterChain liegt und sich nicht irgendwo an einer zufälligen Stelle versteckt.
package com.example.securecontent.security.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
@Configuration
@EnableMethodSecurity // Aktiviert die Verarbeitung von Annotationen wie @PreAuthorize auf Methoden von Spring-Beans
public class MethodSecurityConfig {
// Wir brauchen keine @Bean-Methoden: Der „Einschalter“ der Infrastruktur reicht aus
}
An dieser Stelle sind zwei Dinge wichtig.
Erstens: Diese Klasse muss vom Component Scan von Spring Boot erfasst werden. Wenn sich Ihre Hauptklasse der Anwendung in com.example.securecontent und die Konfigurationsklasse in com.example.securecontent.security.config befindet, ist alles in Ordnung: Spring Boot scannt die Unterpakete, die Klasse wird gesehen und die Annotation funktioniert.
Zweitens: Verwechseln Sie @EnableMethodSecurity nicht mit der Konfiguration der Web-Sicherheit. Sie müssen weiterhin einen SecurityFilterChain haben. Er verschwindet nicht. Die Methodensicherheit ist kein „Ersatz“ für Filter, sondern eine zweite Ebene, die auf der Ebene der Services zu wirken beginnt.
Zum Beispiel könnte Ihre Web-Konfiguration so aussehen (vereinfacht, um die Idee zu zeigen):
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class WebSecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// Regeln auf HTTP-Anfrageebene: werden vor dem Eintreffen im Controller ausgelöst
http.authorizeHttpRequests(auth -> auth
// Öffentlicher Bereich ist für alle zugänglich (einschließlich anonymer Benutzer)
.requestMatchers("/api/public/**").permitAll()
// Alles andere erfordert eine Authentifizierung (aber das ist noch keine „Prüfung der Berechtigung für Geschäftsoperationen“)
.anyRequest().authenticated()
);
// Wichtig: Erst build() erstellt den SecurityFilterChain, der auf Anfragen angewendet wird
return http.build();
}
}
Ja, hier haben wir eine sehr breite Regel gesetzt: anyRequest().authenticated(). Eine solche Konfiguration ist bewusst grob: Wir möchten, dass die Web-Ebene den authentifizierten Benutzer einfach durchlässt, und die Methodenebene stoppt dann den unberechtigten Aufruf. Das ist nicht die endgültige Zugriffslandkarte des Projekts. In einem realen Projekt haben Sie bereits gelernt, präzisere Matcher für /api/editor/** und /api/admin/** zu erstellen, aber diese vereinfachte Variante ist praktisch, um zu überprüfen, ob die Methodensicherheit tatsächlich aktiviert ist und dort „zuschlägt“, wo Sie es erwarten.
Und erst wenn die Methodensicherheit aktiviert ist, werden die Annotationen auf den Methoden nicht mehr nur Dekoration sein.
5. Überprüfung der Methodensicherheit und 401/403
Der ehrlichste Weg, um zu überprüfen, ob alles richtig konfiguriert ist, besteht darin, eine Methode eines Services zu schützen und zu versuchen, sie von einem Benutzer aufzurufen, der nicht über die erforderliche Berechtigung verfügt. Ideal ist es, wenn Ihre Request-Ebene-Regel die Anfrage durchlässt (z. B. nur Authentifizierung verlangt) und die Methodenebene-Regel sie dann stoppt. Dann sehen Sie, dass sich die beiden Ebenen tatsächlich ergänzen.
Stellen wir uns vor, wir haben einen Service für Redaktionsoperationen und möchten die Warteschlange für die Überprüfung durch die Authority 'draft:review' schützen.
package com.example.securecontent.content;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
@Service
public class ReviewService {
// Prüft genau die Berechtigung (Authority) zur Ausführung der Geschäftsoperation
@PreAuthorize("hasAuthority('draft:review')")
public void loadQueue() {
// Hier wird die Geschäftslogik zum Laden der Warteschlange ausgeführt.
// Wenn die Authority nicht vorhanden ist, wird dieser Code nicht erreicht (bei Aufruf über Web kommt 403).
}
}
Und ein Controller, der einfach delegiert:
package com.example.securecontent.content;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class EditorReviewController {
private final ReviewService reviewService;
// Service über Konstruktor injizieren: So erstellt Spring einen Bean, und die Methodensicherheit kann korrekt arbeiten
public EditorReviewController(ReviewService reviewService) {
this.reviewService = reviewService;
}
@GetMapping("/api/editor/review-queue")
public void reviewQueue() {
// Der Controller entscheidet nichts über Berechtigungen: Er ruft einfach die Servicemethode auf,
// und den Zugriff kontrolliert @PreAuthorize auf der Methodenebene.
reviewService.loadQueue();
}
}
Was passiert nun?
Wenn Sie nicht @EnableMethodSecurity aktiviert haben, wird @PreAuthorize(...) nicht ausgeführt, und jeder authentifizierte Benutzer kommt durch (vorausgesetzt, die Request-Ebene-Regeln haben ihn durchgelassen).
Wenn Sie @EnableMethodSecurity aktiviert haben, führt Spring beim Aufruf von loadQueue() eine Authority-Prüfung durch. Und wenn die Authority nicht vorhanden ist, erhalten Sie 403 Forbidden. Das ist ein sehr wichtiger Punkt: Der Benutzer ist bereits authentifiziert (sonst käme 401), aber er hat kein Recht auf die spezifische Operation.
Und hier ist es nützlich, dies mit dem bereits konfigurierten REST-Fehlermodell zu verknüpfen: Ihr AccessDeniedHandler für die REST-API wird genau solche Fälle behandeln und eine vorhersagbare JSON-Antwort liefern. Das heißt, die Methodensicherheit fügt sich automatisch in das bereits aufgebaute Fehlerschema ein: Sie müssen keine Behandlung von AccessDeniedException in jedem Controller extra einbauen.
6. Platzierung von @EnableMethodSecurity in der Konfiguration
Wenn Sie entscheiden, „wohin“ Sie @EnableMethodSecurity legen, gibt es zwei typische Wege, die beide technisch korrekt sind. Der Unterschied liegt in der Lesbarkeit und darin, wie leicht man sich in zwei Wochen im Projekt zurechtfindet (und nach einem Monat werden Sie selbst vermuten, dass nachts jemand Annotationen für Sie setzt).
Der erste Weg: @EnableMethodSecurity direkt auf die Klasse setzen, in der Sie auch SecurityFilterChain deklariert haben. Vorteil: Alles Security-bezogene ist an einem Ort. Nachteil: Eine solche Klasse wird sehr schnell zu einem „Security-Kombigerät“: Filter, Exception Handling, CORS/CSRF, Methodensicherheit – alles in einem Topf.
Der zweite Weg: Die Aktivierung der Methodensicherheit als separate kleine Konfigurationsklasse zu halten, wie wir es oben gemacht haben (MethodSecurityConfig). Dieser Ansatz gefällt mir in einem Lehrprojekt: Er betont, dass die Methodensicherheit eine eigene Schicht ist. Und wenn bei einem Studenten plötzlich @PreAuthorize nicht funktioniert, kann man schnell sagen: „Überprüfe, ob du eine MethodSecurityConfig-Klasse hast und ob sie auch wirklich geladen wird.“
Ein weiterer wichtiger stilistischer Punkt: Man sollte @EnableMethodSecurity nicht auf die Hauptklasse der Anwendung (@SpringBootApplication) setzen. Ja, es würde funktionieren. Aber es entsteht ein „architektonischer Klebstoff“: Ihre Hauptklasse wird zu einem Ort, an dem verschiedene „Einschalter“ zusammengeworfen werden, die nichts mit dem Geschäft der Anwendung zu tun haben. In einem Lehrprojekt versuchen wir, solche Entscheidungen in Paketen wie security/config zu halten, damit die „Schalter“ nebeneinander leben.
7. Typische Fehler bei der Aktivierung der Methodensicherheit
Fehler Nr. 1: @PreAuthorize hinzugefügt, aber @EnableMethodSecurity vergessen.
Das ist das klassischste Szenario. Die Anwendung verhält sich so, als ob die Annotation nicht existiert, und der Anfänger beginnt, alles Mögliche zu „reparieren“: Filtereinstellungen, Rollen, Matcher, Fehlerbehandler. Die richtige Diagnose beginnt mit einer einfachen Frage: Gibt es in der Anwendung eine Klasse mit @EnableMethodSecurity, und wird sie von Spring Boot auch wirklich geladen?
Fehler Nr. 2: Die Konfigurationsklasse liegt außerhalb des Component Scans.
Wenn Sie MethodSecurityConfig in ein Paket legen, das von Spring Boot nicht gescannt wird (z. B. in ein benachbartes Root-Paket oder ein Modul, das nicht eingebunden ist), wird @EnableMethodSecurity einfach nicht wirksam. Äußerlich sieht das genauso aus wie Fehler Nr. 1: Annotationen werden „ignoriert“. Halten Sie daher Ihre Konfigurationsklassen innerhalb des Basispakets der Anwendung, z. B. com.example.securecontent.security.config.
Fehler Nr. 3: Sie versuchen, eine Methode in einem Objekt zu schützen, das nicht von Spring erzeugt wurde.
Die Methodensicherheit funktioniert auf Spring-Beans. Wenn Sie irgendwo new ReviewService(...) verwenden (oder einen Service manuell in Testcode ohne Kontext erstellen), gibt es keine Proxys und keine Prüfungen. In einem Lehrprojekt kann man das leicht vermeiden: Services sollten immer mit @Service annotiert sein, und Abhängigkeiten sollten über den Konstruktor hereinkommen.
Fehler Nr. 4: Sie verlagern Regeln in die Methodensicherheit und entfernen die Request-Ebene-Regeln vollständig.
Manchmal denkt ein Student: „Da es @PreAuthorize gibt, brauche ich die Filter nicht mehr.“ Das ist ein gefährliches Ungleichgewicht. Die Request-Ebene bleibt die erste Verteidigungslinie: Sie filtert schnell anonyme Benutzer heraus, schützt technische Bereiche (/api/admin/**) und liefert eine vorhersagbare Zugriffslandkarte nach URL. Die Methodensicherheit verstärkt sie und schließt geschäftliche Sicherheitslücken, ersetzt sie aber nicht.
Fehler Nr. 5: Sie annotieren alles Mögliche, einschließlich interner „Hilfs“-Methoden.
Die Methodensicherheit funktioniert am besten, wenn Regeln auf verständlichen öffentlichen Einstiegsmethoden des Services stehen, wo die Geschäftsoperation tatsächlich beginnt. Wenn Sie anfangen, jede zweite private oder kleine Methode mit Annotationen zu versehen, wird die Konfiguration verrauscht, und einige Prüfungen verhalten sich unerwartet (warum genau – das werden wir im Detail in der nächsten Vorlesung dieses Tages über Proxys und Self-Invocation besprechen).
GO TO FULL VERSION