CodeGym /Kurse /Spring Security /ROLE_: Rollen und authorities

ROLE_: Rollen und authorities

Spring Security
Level 7, Lektion 3
Verfügbar

1. Bedeutung des Präfixes ROLE_

Wenn Sie zum ersten Mal ROLE_ADMIN sehen, möchten Sie fragen: „Was ist das, ein Passwort für das Admin-Panel? Oder eine geheime Aufschrift an der Tür zum Serverraum?“ In Wirklichkeit ist es prosaischer: Es handelt sich um eine Namenskonvention. Spring Security arbeitet intern mit Zeichenketten von Berechtigungen, und historisch wurden Rollen als normale „authorities“ gespeichert, jedoch mit dem Präfix ROLE_.

An diesem Punkt haben wir bereits ein rollenbasiertes Modell von Zonen und punktuelle authorities für Aktionen. Es bleibt eine letzte Verwirrung: Warum sieht derselbe Zugang an einer Stelle wie ADMIN aus, an einer anderen wie ROLE_ADMIN.

In Spring Security gibt es das Konzept des GrantedAuthority – wörtlich „gewährte Berechtigung“. Und fast überall, wo es um Zugriff geht, werden letztlich Zeichenketten verglichen. Eine Rolle ist keine separate Entität im luftleeren Raum, sondern eine authority mit einem speziellen „Etikett“ in Form eines Präfixes. Daher ist ROLE_ADMIN eine Zeichenkette, die bedeutet: „Der Benutzer hat die Rolle ADMIN.“

Es ist wichtig, eine Schlüsselregel zu merken: Wenn Sie ROLE_ sehen ..., ist das kein „anderes“ Berechtigungssystem, es ist dieselbe Liste von Berechtigungen, nur die Rolle wird durch das Präfix hervorgehoben. Und die Methoden hasRole(...) existieren unter anderem zur Bequemlichkeit, damit Sie den menschlichen Rollennamen ohne ROLE_ schreiben können, und Spring Security fügt das Präfix selbst hinzu. In der offiziellen Dokumentation wird direkt gesagt, dass hasRole ein Shortcut zur Überprüfung der authority mit automatischem Präfix ROLE_ ist.

Ein kleines Beispiel „an den Fingern“, um zu sehen, was eine authority als Zeichenkette ist:

import org.springframework.security.core.authority.SimpleGrantedAuthority;

// Authority in Spring Security ist buchstäblich eine Zeichenkette, die später beim Zugriffscheck verglichen wird
SimpleGrantedAuthority a = new SimpleGrantedAuthority("ROLE_ADMIN");

// Es wird dieselbe Zeichenkette zurückgegeben, die wir an den Konstruktor übergeben haben
System.out.println(a.getAuthority()); // ROLE_ADMIN

Hier gibt es keine Magie: „Berechtigung“ ist die Zeichenkette "ROLE_ADMIN". Spring Security vergleicht dann solche Zeichenketten mit dem, was Sie in den Zugriffsregeln angefordert haben.

2. Zwei Modi: role-style und exact-string

Die Hauptquelle der Verwirrung in diesem Thema ist, dass Spring Security Ihnen zwei „Stile“ bietet, die ähnlich aussehen, sich aber unterschiedlich verhalten. Es ist wie zwei Modi desselben Werkzeugs: Einer erledigt eine kleine Arbeit für Sie, der andere nimmt alles wörtlich. Wenn Sie sie verwechseln, erhalten Sie die Situation „Ich bin definitiv Admin, aber ich werde nicht reingelassen“, und dann beginnt die Phase „Ich versuche es nochmal und deaktiviere Security“.

Der erste Modus ist der role-style. Sie schreiben den „reinen Rollennamen“, z. B. ADMIN, und Spring Security versteht selbst, dass es eigentlich mit "ROLE_ADMIN" vergleichen muss. Dies betrifft hasRole("ADMIN") und roles("ADMIN") beim Benutzer-Builder.

Der zweite Modus ist der exact-string. Sie schreiben die Zeichenkette „wie sie ist“, und Spring Security vergleicht genau diese, ohne Interpretationen. Dies betrifft hasAuthority(...) und authorities(...) beim Erstellen eines Benutzers.

Wichtiger Hinweis: hasRole ist keine „Überprüfung einer anderen Entität“. Es ist immer noch dasselbe GrantedAuthority, nur mit automatischem Präfix.

Um das Lesen zu erleichtern, behalten Sie eine kleine Tabelle im Kopf (wie einen „Übersetzer“ von role-style in exact-string):

Wo Sie die Regel schreiben Was Sie schreiben Was tatsächlich geprüft/gespeichert wird
hasRole("ADMIN") ADMIN "ROLE_ADMIN"
hasAuthority("ROLE_ADMIN") ROLE_ADMIN "ROLE_ADMIN"
hasAuthority("draft:publish") draft:publish "draft:publish"

Beachten Sie: draft:publish ist überhaupt keine Rolle. Es ist einfach eine punktuelle Berechtigung, und es wird kein ROLE_ daran angehängt. Es wird als normale Zeichenkette verglichen.

3. roles(...) und authorities(...) beim Benutzer

Wenn wir In-Memory-Benutzer erstellen, legen wir praktisch manuell den „Pass“ des Benutzers für Spring Security an. Dabei ist es wichtig, nicht zu verwechseln, welches Verfahren welches Format erwartet. Anfänger denken oft: „Das ist doch nur eine Liste von Berechtigungen, was macht das für einen Unterschied.“ Es gibt einen Unterschied – und der liegt genau im ROLE_.

Wenn Sie roles("ADMIN") verwenden, schreiben Sie die Rolle ohne Präfix, und Spring Security wandelt dies unter der Haube in "ROLE_ADMIN" um. Dies ist praktisch, wenn Sie Rollen genau als Rollen speichern und ROLE_ nicht von Hand sehen möchten.

Wenn Sie authorities("ROLE_ADMIN") verwenden, legen Sie die Zeichenkette exakt so ab, wie sie gespeichert und verglichen wird. Dies ist praktisch, wenn Sie in einem Benutzer Rollen und punktuelle Berechtigungen mischen – denn punktuelle Berechtigungen werden ohnehin als exact-strings geschrieben.

Hier ist eine minimale Demonstration des Unterschieds.

Variante A: role-style (Präfix wird automatisch hinzugefügt)

import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;

// roles("ADMIN") bedeutet: tatsächlich die authority "ROLE_ADMIN" speichern
UserDetails admin = User.builder()
    .username("root")
    .password("{bcrypt}...") // hier nur ein Beispiel: im echten Code speichern Sie einen gültigen Hash
    .roles("ADMIN") // Präfix ROLE_ wird automatisch hinzugefügt
    .build();

Hier wird die Rolle als "ADMIN" angegeben, und Spring Security wandelt sie intern in "ROLE_ADMIN" um.

Variante B: exact-string (Sie schreiben alles so, wie es ist)

import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;

// authorities(...) ist exact-string: was Sie schreiben, wird so in die Berechtigungsliste aufgenommen
UserDetails admin = User.builder()
    .username("root")
    .password("{bcrypt}...") // hier nur ein Beispiel: im echten Code speichern Sie einen gültigen Hash
    .authorities("ROLE_ADMIN") // Präfix fügen Sie selbst hinzu
    .build();

Hier sind Sie selbst für das Präfix verantwortlich. Und das ist in Ordnung. Wichtig ist nur, nicht zu vergessen: Wenn Sie "ADMIN" in authorities(...) schreiben, wird dies die authority "ADMIN" sein, nicht die Rolle "ROLE_ADMIN".

Übrigens sieht man in offiziellen Beispielen für In-Memory-Konfigurationen, dass Rollen als authority letztlich genau so aussehen: ROLE_USER, ROLE_ADMIN.

4. hasRole(...) und hasAuthority(...) in der SecurityFilterChain

In der SecurityFilterChain schreiben wir Regeln, als würden wir eine U-Bahn-Karte zeichnen: Hier der öffentliche Ast, hier der User-Ast, hier der Editor-Ast, und dort die „Admin-Station, Vorsicht, Türen schließen automatisch“. Und an dieser Stelle schießt ROLE_ besonders schmerzhaft ins Bein: Ein falsches Wort – und plötzlich haben Sie den geheimen Modus „Admin ist kein Admin“.

Schlüsselfakt: hasRole("ADMIN") fügt automatisch ROLE_ zu dem hinzu, was Sie übergeben haben. Die Dokumentation formuliert es ganz direkt: Die Rolle sollte nicht mit ROLE_ beginnen, weil sie ohnehin automatisch um dieses Präfix ergänzt wird.

Praktisch bedeutet das:

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 {
    return http.authorizeHttpRequests(a -> a
        // hasRole("ADMIN") -> unter der Haube wird die authority "ROLE_ADMIN" geprüft
        .requestMatchers("/api/admin/**").hasRole("ADMIN")
        // hasAuthority(...) -> exakte Zeichenkette, ohne Präfixe oder Interpretationen
        .requestMatchers("/api/editor/drafts/*/publish").hasAuthority("draft:publish")
    ).build();
}

Die erste Zeile bedeutet: „Lasse nur denjenigen durch, der die authority "ROLE_ADMIN" besitzt.“ Die zweite Zeile bedeutet: „Lasse nur denjenigen durch, der die authority "draft:publish" besitzt.“

Und nun der klassische „Funktioniert nicht, warum?“-Fall:

// Falsch: hasRole fügt selbst ROLE_ hinzu, es würde ROLE_ROLE_ADMIN werden
.requestMatchers("/api/admin/**").hasRole("ROLE_ADMIN")

Warum falsch? Weil hasRole das Präfix noch einmal hinzufügt und tatsächlich nach "ROLE_ROLE_ADMIN" sucht. Und ja, eine solche Berechtigung hat der Benutzer natürlich nicht (es sei denn, Sie haben beschlossen, eine Maskerade zu veranstalten).

Ein symmetrischer Fehler in die andere Richtung:

// Fast immer falsch: es wird die authority "ADMIN" geprüft, aber die Rolle wird meist als "ROLE_ADMIN" gespeichert
.requestMatchers("/api/admin/**").hasAuthority("ADMIN")

Dies würde die authority "ADMIN" prüfen, während Ihre Rollen normalerweise als "ROLE_ADMIN" existieren. Sie fordern also etwas an, aber in der Liste liegt etwas anderes.

5. Algorithmus zum Lesen von Rollen und authorities

Die Verwirrung mit ROLE_ entsteht selten aufgrund der Komplexität von Spring Security. Sie entsteht, weil unser Gehirn gerne „rätselt“. Wir sehen ADMIN und denken automatisch an den Administrator. Spring Security denkt nicht – es vergleicht Zeichenketten. Daher ist es nützlich, einen kleinen Lesealgorithmus zu haben, wie ein erfahrener Redakteur: „Wenn das Wort so geschrieben ist – lesen wir es so.“

Stellen Sie sich vor, Sie öffnen die Konfiguration nach einem Monat und möchten verstehen, was passiert, ohne die Anwendung zu starten. Sie können buchstäblich der Regel folgen: Wenn Sie hasRole sehen, fügen Sie gedanklich ROLE_ hinzu; wenn Sie hasAuthority sehen, nehmen Sie die Zeichenkette als genauen Wert; wenn Sie roles(...) sehen, wird das Präfix automatisch hinzugefügt; wenn authorities(...), wird es kein Präfix geben, solange Sie es nicht selbst schreiben.

Hier ist ein einfaches Flussdiagramm, das hilft, „doppeltes Präfix“ und „verlorenes Präfix“ zu vermeiden:

flowchart TD
    A["Betrachte die Zugriffsregel"] --> B{"hasRole(...) ?"}
    B -- Ja --> C["Übergebener Wert = Rollenname ohne ROLE_"]
    C --> D["Framework prüft ROLE_ + Wert"]
    B -- Nein --> E{"hasAuthority(...) ?"}
    E -- Ja --> F["Es wird die genaue Zeichenkette wie angegeben geprüft"]
    E -- Nein --> G["Suche nach anderer Prüfung (permitAll/authenticated/denyAll)"]

Wenn Ihnen das wie ein zu „kindischer“ Algorithmus vorkommt – gut. Genau solche Dinge retten in echten Projekten, wenn Sie am Freitagabend den Zugriff auf der Produktion reparieren und sehr gerne weiterleben möchten.

6. Naming ändert sich hier nicht, nur die Übersetzung der Rolle in einen String

Die Konventionen selbst sind bereits festgelegt und erfordern kein neues Schema. Die Rollen bleiben USER / EDITOR / ADMIN, und die punktuellen authorities haben die exact-string-Form resource:action, z. B. draft:publish. Neu in dieser Lektion ist nur eines: Wenn eine Rolle als authority-Zeichenkette gespeichert oder geprüft wird, erhält sie das Präfix ROLE_; punktuelle authorities bleiben wie sie sind.

Daher ist die praktische Regel einfach. Wenn Sie mit hasRole(...) oder roles(...) arbeiten, schreiben Sie den menschlichen Rollennamen ohne Präfix. Wenn Sie mit hasAuthority(...) oder authorities(...) arbeiten, schreiben Sie genau die Zeichenkette, die der Benutzer haben soll: ROLE_ADMIN, ROLE_EDITOR, draft:publish. Das reicht bereits aus, um eine gemeinsame Matrix ohne doppelte Präfixe und verlorene Berechtigungen zu erstellen.

7. Beispiel: Zonen und punktuelle Berechtigungen

Derzeit haben wir ein lehrreiches, aber bereits „produktionsreifes“ Bild: Es gibt eine öffentliche Zone, eine private Benutzerzone, eine Editorzone und eine Adminzone. Für Zonen ist es praktisch, Rollen zu verwenden: Sie sind kurz und gut lesbar. Für einzelne Aktionen (z. B. Veröffentlichung eines Entwurfs) kann eine authority jedoch ausdrucksstärker sein, weil die Rolle EDITOR möglicherweise zu breit ist.

Unten ist ein kleiner Ausschnitt, der genau diesen Ansatz zeigt: Zonen via hasRole und punktuelle Aktionen via hasAuthority. Beachten Sie die Reihenfolge: Die punktuelle Veröffentlichungsregel setzen wir oberhalb der allgemeinen Editorzone, sonst würde der allgemeine Matcher die Anfrage vorher abfangen. Und noch eine wichtige Kleinigkeit: In hasRole übergeben wir ADMIN, nicht ROLE_ADMIN, weil das Präfix automatisch hinzugefügt wird.

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 {
    return http.authorizeHttpRequests(auth -> auth
        // Anwendungszonen (Routen) mit Rollen einschränken
        .requestMatchers("/api/me/**").hasRole("USER")

        // Punktuelle Aktion oberhalb der allgemeinen Editorzone setzen
        .requestMatchers("/api/editor/drafts/*/publish").hasAuthority("draft:publish")
        .requestMatchers("/api/editor/**").hasRole("EDITOR")

        // Adminzone – Rolle ADMIN (Prüfung erfolgt auf "ROLE_ADMIN")
        .requestMatchers("/api/admin/**").hasRole("ADMIN")
    ).build();
}

Und damit dies tatsächlich funktioniert, muss der In-Memory-Benutzer genau die Zeichenketten besitzen, die diese Prüfungen erwarten. Wenn Sie den exact-string-Stil in authorities(...) gewählt haben, müssen Rollen als ROLE_... gespeichert werden (und das ist in Ordnung):

import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;

// Hier geben wir die authorities explizit als Zeichenketten an (exact-string)
UserDetails editor = User.builder()
    .username("eva")
    .password("{bcrypt}...") // hier nur ein Beispiel: im echten Code speichern Sie einen gültigen Hash
    // Rollen in der authorities-Liste mit ROLE_ speichern, punktuelle Berechtigung wie gehabt
    .authorities("ROLE_USER", "ROLE_EDITOR", "draft:publish")
    .build();

Und hier ist alles ohne Mysterien lesbar: Die Rollen-Zeichenketten beginnen mit ROLE_, die punktuelle Berechtigung ohne Präfix.

8. Typische Fehler mit ROLE_

Fehler Nr. 1: hasRole("ROLE_ADMIN") schreiben.
Diese Schreibweise erscheint logisch, weil Sie ROLE_ADMIN irgendwo in Logs oder in authorities(...) gesehen haben. Aber hasRole fügt selbst ROLE_ hinzu, und letztendlich wird die Prüfung zu einer Erwartung von ROLE_ROLE_ADMIN. Die Dokumentation warnt ausdrücklich davor, dass die Rolle nicht mit ROLE_ beginnen sollte, da das Präfix automatisch hinzugefügt wird.

Fehler Nr. 2: hasAuthority("ADMIN") schreiben, wenn der Benutzer die Rolle als ROLE_ADMIN speichert.
hasAuthority ist eine exact-string-Prüfung. Sie „errät“ nicht, dass Sie die Rolle meinten, und fügt kein ROLE_ hinzu. Wenn beim Benutzer "ROLE_ADMIN" hinterlegt ist, Sie aber "ADMIN" prüfen, sind das für Spring Security zwei verschiedene Berechtigungen. Und es wird ehrlich den Zugriff verweigern, obwohl Sie „im Herzen“ den Benutzer als Admin betrachten.

Fehler Nr. 3: Naming ohne explizite Konvention mischen.
Manchmal wird die Rolle an einer Stelle ADMIN, an einer anderen admin, an einer dritten ROLE_admin genannt. Die Zeichenketten ähneln sich, sind aber nicht identisch. Irgendwann beginnen Sie, den Zugriff durch „Buchstabenvertauschen“ zu reparieren, als wäre es ein WLAN-Passwort in einem Café. Es ist besser, sich einmal zu einigen: Rollen sind USER/EDITOR/ADMIN, und als authority ist die Rolle ROLE_..., und danach einfach den Stil beibehalten.

Fehler Nr. 4: Versuchen zu „erraten“, was die Methode tut, anstatt sich zu merken: hasRole = Shortcut.
Wenn Sie hasRole als separate Magie betrachten, werden Sie sich unweigerlich verwirren, wenn Sie daneben hasAuthority("draft:publish") sehen. Das richtige Bild ist einfacher: hasRole ist nur eine bequemere Schreibweise für die Prüfung einer authority mit ROLE_-Präfix.

1
Aufgabe
Spring Security, Level 7, Lektion 3
Gesperrt
`roles("ADMIN")` und `hasRole("ADMIN")` ohne manuelles `ROLE_`
`roles("ADMIN")` und `hasRole("ADMIN")` ohne manuelles `ROLE_`
1
Aufgabe
Spring Security, Level 7, Lektion 3
Gesperrt
Dieselbe Rolle durch `hasRole` und `hasAuthority`
Dieselbe Rolle durch `hasRole` und `hasAuthority`
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION