Zugriffs-Dekoratoren
Sobald ein Benutzer authentifiziert ist, sichern Sie Routen ab. Rustango
liefert Djangos @login_required-Familie als komponierbare axum-Layer:
hängen Sie einen an einen Router, und anonyme Anfragen werden abgewiesen — per
302 auf Ihre Anmeldeseite umgeleitet (Browser-Ablauf) oder mit 401/403
beantwortet (API-Ablauf) —, bevor sie je den Handler erreichen.
Quelle:
rustango::auth_decorators(login_required,login_required_or_401,user_passes_test,superuser_required,active_required,permission_required+ die_or_403-Varianten;safe_next,extract_next) — hinter dem Featuretenancy(die Gatter lesen denSessionUser-Extraktor).Ausführbare Version: das Absicherungsverhalten wird vom getesteten
auth_demoabgedeckt —cargo test -p auth_demo --test auth_decorators.
Ein Begriff hier neu? Middleware/Layer, Extraktor, 401/403 — siehe das Glossar.
Vertiefungsbegleiter zum Sicherheitsleitfaden. Die Gatter lesen die bei der Anmeldung gesetzte Session — siehe Sessions.
Inhaltsverzeichnis
- Schnellstart · Browser- vs. API-Gatter
- Die Gatter-Familie · Prädikat- und Rollen-Gatter
- Berechtigungs-Gatter · Der
?next=-Umlauf - Hinweise und Grenzen
Schnellstart
use rustango::auth_decorators::login_required;
// Scope the gate to a sub-router (the idiomatic shape):
let private = Router::new()
.route("/profile", get(profile))
.route("/settings", get(settings))
.layer(login_required("/login")); // anonymous → 302 /login?next=...
let app = Router::new()
.route("/", get(home)) // public
.merge(private);
Anonyme Anfragen an /profile werden auf /login?next=%2Fprofile umgeleitet;
eine authentifizierte Anfrage geht bis zum Handler durch.
Browser- vs. API-Gatter
Dasselbe Gatter kommt in zwei Antwortformen. Wählen Sie danach, was der Aufrufer mit der Antwort anfangen kann:
- Browser / HTML → die Basis-Gatter leiten per 302 auf Ihre Anmeldeseite um (ein Mensch kann ihr folgen und sich anmelden).
- JSON-API → die
_or_403-Familie gibt Statuscodes zurück:401 Unauthorizedfür anonym,403 Forbiddenfür authentifiziert-aber-nicht-erlaubt (ein Client kann keine HTML-Anmeldeseite rendern, und die 401/403-Aufteilung lässt ihn „anmelden" von „das darfst du nicht" unterscheiden).
// Browser: redirect to /login
let app = Router::new().route("/dashboard", get(dash)).layer(login_required("/login"));
// API: 401 for anonymous, never a redirect
let api = Router::new().route("/api/me", get(me)).layer(login_required_or_401());
Die Gatter-Familie
| Gatter (Browser, 302) | API-Variante (401/403) | Lässt durch |
|---|---|---|
login_required(url) | login_required_or_401() | jeden angemeldeten Benutzer |
active_required(url) | active_required_or_403() | angemeldet und active |
superuser_required(url) | superuser_required_or_403() | is_superuser && active |
user_passes_test(url, pred) | user_passes_test_or_403(pred) | Prädikat über die User-Zeile |
permission_required(url, codename) | permission_required_or_403(codename) | hält den Berechtigungs-Codename |
Alle sind tower-Layer — .layer(...) sie auf einen Router oder Sub-Router.
Prädikat- und Rollen-Gatter
user_passes_test führt Ihre Closure gegen die aufgelöste User-Zeile aus,
sodass Sie über jedes Feld absichern können:
use rustango::auth_decorators::{user_passes_test, superuser_required_or_403};
// Staff-only sub-router (browser):
let staff = Router::new()
.route("/admin/dashboard", get(dashboard))
.layer(user_passes_test("/login", |u| u.is_superuser));
// Superuser-only JSON API → 401 anonymous / 403 non-superuser:
let api = Router::new()
.route("/api/admin/stats", get(stats))
.layer(superuser_required_or_403());
superuser_required / active_required sind fest verdrahtete Abkürzungen für
die gängigen Prädikate is_superuser && active / active, damit Aufrufstellen
nicht stillschweigend darüber auseinanderdriften, ob deaktivierte Konten noch
zählen.
Berechtigungs-Gatter
permission_required prüft einen Berechtigungs-Codename gegen die
Berechtigungs-Engine des Mandanten (Superuser umgehen sie automatisch). Es löst
zusätzlich den Tenant-Extraktor auf, sodass Routen, die es verwenden, unter dem
Mandantenkontext eingehängt werden müssen:
use rustango::auth_decorators::permission_required;
use rustango::tenancy::permissions::ACCESS_ADMIN_CODENAME;
let admin = Router::new()
.route("/admin", get(dashboard))
.layer(permission_required("/login", ACCESS_ADMIN_CODENAME));
Der ?next=-Umlauf
login_required bewahrt die ursprünglich angeforderte URL in ?next= auf,
sodass Ihr Anmelde-Handler den Benutzer nach der Authentifizierung zurückschicken
kann. Sie müssen diesen Wert bereinigen — ihn ungeprüft in eine Umleitung zu
spiegeln, ist ein lehrbuchmäßiges Open-Redirect-Loch (Phishing). safe_next ist
der Schutz:
use rustango::auth_decorators::{extract_next, safe_next};
async fn login_post(Query(q): Query<HashMap<String, String>>, /* … */) -> Response {
// … verify credentials, set the session …
let dest = extract_next(&q)
.and_then(|n| safe_next(&n)) // rejects open redirects
.unwrap_or_else(|| "/".to_owned());
Redirect::to(&dest).into_response()
}
safe_next akzeptiert nur gleichherkünftige, wurzelrelative Pfade — es weist
absolute URLs, schema-relative //host, Backslash-Varianten und deren
prozentkodierte Formen zurück:
assert_eq!(safe_next("/dashboard"), Some("/dashboard".to_owned()));
assert_eq!(safe_next("https://evil.example/x"), None);
assert_eq!(safe_next("//evil.example/x"), None); // scheme-relative
assert_eq!(safe_next("%2F%2Fevil.example/x"), None); // decodes to //evil
Hinweise und Grenzen
- Diese Gatter lesen die Session. „Angemeldet" bedeutet, dass der
SessionUser-Extraktor einen Benutzer aus dem Session-Cookie aufgelöst hat — sie sind also für Session-/Cookie-Auth gedacht. API-Token-Auth (JWT, API-Schlüssel) sichert stattdessen auf der Ebene der Backend-Kette ab und liest denAuthorization-Header. - Die Layer-Reihenfolge ist wichtig.
.layer(gate)schützt jede Route, die dem Router vor ihm hinzugefügt wurde; danach hinzugefügte Routen sind öffentlich. Das Gatter auf einen dedizierten Sub-Router zu beschränken (die Schnellstart-Form) vermeidet diesen Fallstrick. permission_requiredbenötigt Mandantenkontext (es fragt die Mandanten-Berechtigungs-Engine ab) — hängen Sie es unter den Mandanten ein; eine Route ohne Mandant quittiert mit 500.- Das
?next=der Umleitung ist stets prozentkodiert, sodass CRLF / Response-Splitting nicht in denLocation-Header durchsickern kann.
