Schütze deine Actix Web API mit rollenbasierter Zugangskontrolle (RBAC) und JWT-Validierung
Diese Anleitung hilft dir, Autorisierung zu implementieren, um deine Actix Web APIs mit rollenbasierter Zugangskontrolle (RBAC) und JSON Web Tokens (JWTs), die von Logto ausgestellt werden, abzusichern.
Bevor du beginnst
Deine Client-Anwendungen müssen Zugangstokens (Access tokens) von Logto erhalten. Falls du die Client-Integration noch nicht eingerichtet hast, schaue dir unsere Schnellstarts für React, Vue, Angular oder andere Client-Frameworks an oder sieh dir unseren Maschine-zu-Maschine-Leitfaden für Server-zu-Server-Zugriff an.
Dieser Leitfaden konzentriert sich auf die serverseitige Validierung dieser Tokens in deiner Actix Web-Anwendung.

Was du lernen wirst
- JWT-Validierung: Lerne, Zugangstokens (Access tokens) zu validieren und Authentifizierungsinformationen zu extrahieren
- Middleware-Implementierung: Erstelle wiederverwendbare Middleware zum Schutz deiner API
- Berechtigungsmodelle: Verstehe und implementiere verschiedene Autorisierungsmuster (Authorization patterns):
- Globale API-Ressourcen für anwendungsweite Endpunkte
- Organisationsberechtigungen für mandantenspezifische Funktionskontrolle
- Organisationsbezogene API-Ressourcen für Multi-Tenant-Datenzugriff
- RBAC-Integration: Erzwinge rollenbasierte Berechtigungen (Role-based permissions) und Berechtigungen (Scopes) in deinen API-Endpunkten
Voraussetzungen
- Neueste stabile Version von Rust installiert
- Grundlegendes Verständnis von Actix Web und Web-API-Entwicklung
- Eine konfigurierte Logto-Anwendung (siehe Schnellstarts, falls benötigt)
Überblick über Berechtigungsmodelle
Bevor du Schutzmechanismen implementierst, wähle das Berechtigungsmodell, das zu deiner Anwendungsarchitektur passt. Dies steht im Einklang mit den drei Haupt-Autorisierungsszenarien von Logto:
- Globale API-Ressourcen
- Organisations-(Nicht-API-)Berechtigungen
- Organisationsbezogene API-Ressourcen

- Anwendungsfall: Schutz von API-Ressourcen, die in deiner gesamten Anwendung geteilt werden (nicht organisationsspezifisch)
- Token-Typ: Zugangstoken (Access token) mit globaler Zielgruppe (Audience)
- Beispiele: Öffentliche APIs, Kernproduktdienste, Admin-Endpunkte
- Am besten geeignet für: SaaS-Produkte mit APIs, die von allen Kunden genutzt werden, Microservices ohne Mandantenisolation
- Mehr erfahren: Globale API-Ressourcen schützen

- Anwendungsfall: Steuerung von organisationsspezifischen Aktionen, UI-Funktionen oder Geschäftslogik (keine APIs)
- Token-Typ: Organisationstoken mit organisationsspezifischer Zielgruppe (Audience)
- Beispiele: Feature-Gating, Dashboard-Berechtigungen, Steuerung von Mitglieder-Einladungen
- Am besten geeignet für: Multi-Tenant-SaaS mit organisationsspezifischen Funktionen und Workflows
- Mehr erfahren: Organisations-(Nicht-API-)Berechtigungen schützen

- Anwendungsfall: Schutz von API-Ressourcen, die innerhalb eines bestimmten Organisationskontextes zugänglich sind
- Token-Typ: Organisationstoken mit API-Ressourcen-Zielgruppe (Audience) + Organisationskontext
- Beispiele: Multi-Tenant-APIs, organisationsbezogene Datenendpunkte, mandantenspezifische Microservices
- Am besten geeignet für: Multi-Tenant-SaaS, bei denen API-Daten organisationsbezogen sind
- Mehr erfahren: Organisationsbezogene API-Ressourcen schützen
💡 Wähle dein Modell, bevor du fortfährst – die Umsetzung in diesem Leitfaden bezieht sich durchgehend auf deinen gewählten Ansatz.
Schnelle Vorbereitungsschritte
Logto-Ressourcen & Berechtigungen konfigurieren
- Globale API-Ressourcen
- Organisations-(Nicht-API-)Berechtigungen
- Organisationsbezogene API-Ressourcen
- API-Ressource erstellen: Gehe zu Konsole → API-Ressourcen und registriere deine API (z. B.
https://api.yourapp.com
) - Berechtigungen definieren: Füge Berechtigungen wie
read:products
,write:orders
hinzu – siehe API-Ressourcen mit Berechtigungen definieren - Globale Rollen erstellen: Gehe zu Konsole → Rollen und erstelle Rollen, die deine API-Berechtigungen enthalten – siehe Globale Rollen konfigurieren
- Rollen zuweisen: Weisen Sie Benutzern oder M2M-Anwendungen, die API-Zugriff benötigen, Rollen zu
- Organisationsberechtigungen definieren: Erstelle nicht-API-bezogene Organisationsberechtigungen wie
invite:member
,manage:billing
in der Organisationstemplate - Organisationsrollen einrichten: Konfiguriere die Organisationstemplate mit organisationsspezifischen Rollen und weise ihnen Berechtigungen zu
- Organisationsrollen zuweisen: Weisen Sie Benutzern innerhalb jedes Organisationskontexts Organisationsrollen zu
- API-Ressource erstellen: Registriere deine API-Ressource wie oben, aber sie wird im Organisationskontext verwendet
- Berechtigungen definieren: Füge Berechtigungen wie
read:data
,write:settings
hinzu, die auf den Organisationskontext beschränkt sind - Organisationstemplate konfigurieren: Richte Organisationsrollen ein, die deine API-Ressourcenberechtigungen enthalten
- Organisationsrollen zuweisen: Weisen Sie Benutzern oder M2M-Anwendungen Organisationsrollen zu, die API-Berechtigungen enthalten
- Multi-Tenant-Setup: Stelle sicher, dass deine API organisationsbezogene Daten und Validierung verarbeiten kann
Beginne mit unserem Leitfaden zur rollenbasierten Zugangskontrolle (RBAC) für eine Schritt-für-Schritt-Anleitung.
Aktualisiere deine Client-Anwendung
Fordere die passenden Berechtigungen in deinem Client an:
- Benutzer-Authentifizierung: App aktualisieren →, um deine API-Berechtigungen und/oder Organisationskontext anzufordern
- Maschine-zu-Maschine: M2M-Berechtigungen konfigurieren → für Server-zu-Server-Zugriff
Der Prozess beinhaltet in der Regel die Aktualisierung deiner Client-Konfiguration, um eines oder mehrere der folgenden Elemente einzuschließen:
scope
-Parameter in OAuth-Flowsresource
-Parameter für den Zugriff auf API-Ressourcenorganization_id
für den Organisationskontext
Stelle sicher, dass der Benutzer oder die M2M-App, die du testest, die entsprechenden Rollen oder Organisationsrollen zugewiesen bekommen hat, die die notwendigen Berechtigungen für deine API enthalten.
Initialisiere dein API-Projekt
Um ein neues Actix Web-Projekt zu initialisieren, erstelle ein Verzeichnis und richte die Grundstruktur ein:
cargo new your-api-name
cd your-api-name
Füge die Actix Web-Abhängigkeiten zu deiner Cargo.toml
hinzu:
[dependencies]
actix-web = "4.0"
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Erstelle eine grundlegende Actix Web-Anwendung:
use actix_web::{web, App, HttpServer, Result};
use serde_json::{json, Value};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/", web::get().to(hello_handler))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
async fn hello_handler() -> Result<web::Json<Value>> {
Ok(web::Json(json!({ "message": "Hello from Actix Web" })))
}
Starte den Entwicklungsserver:
cargo run
Weitere Informationen zum Einrichten von Routen, Middleware und anderen Funktionen findest du in der Actix Web-Dokumentation.
Initialisiere Konstanten und Hilfsfunktionen
Definiere die notwendigen Konstanten und Hilfsfunktionen in deinem Code, um die Extraktion und Validierung von Tokens zu handhaben. Eine gültige Anfrage muss einen Authorization
-Header in der Form Bearer <Zugangstoken (Access token)>
enthalten.
use serde::{Deserialize, Serialize};
use std::fmt;
pub const JWKS_URI: &str = "https://your-tenant.logto.app/oidc/jwks";
pub const ISSUER: &str = "https://your-tenant.logto.app/oidc";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthInfo {
pub sub: String,
pub client_id: Option<String>,
pub organization_id: Option<String>,
pub scopes: Vec<String>,
pub audience: Vec<String>,
}
impl AuthInfo {
pub fn new(
sub: String,
client_id: Option<String>,
organization_id: Option<String>,
scopes: Vec<String>,
audience: Vec<String>,
) -> Self {
Self {
sub,
client_id,
organization_id,
scopes,
audience,
}
}
}
#[derive(Debug)]
pub struct AuthorizationError {
pub message: String,
pub status_code: u16,
}
impl AuthorizationError {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
status_code: 403,
}
}
pub fn with_status(message: impl Into<String>, status_code: u16) -> Self {
Self {
message: message.into(),
status_code,
}
}
}
impl fmt::Display for AuthorizationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for AuthorizationError {}
pub fn extract_bearer_token(authorization: Option<&str>) -> Result<&str, AuthorizationError> {
let auth_header = authorization.ok_or_else(|| {
AuthorizationError::with_status("Authorization-Header fehlt", 401)
})?;
if !auth_header.starts_with("Bearer ") {
return Err(AuthorizationError::with_status(
"Authorization-Header muss mit \"Bearer \" beginnen",
401,
));
}
Ok(&auth_header[7..]) // Entferne das Präfix 'Bearer '
}
Informationen über deinen Logto-Mandanten abrufen
Du benötigst die folgenden Werte, um von Logto ausgestellte Tokens zu validieren:
- JSON Web Key Set (JWKS) URI: Die URL zu den öffentlichen Schlüsseln von Logto, die zur Überprüfung von JWT-Signaturen verwendet wird.
- Aussteller (Issuer): Der erwartete Ausstellerwert (die OIDC-URL von Logto).
Zuerst finde den Endpunkt deines Logto-Tenants. Du findest ihn an verschiedenen Stellen:
- In der Logto-Konsole unter Einstellungen → Domains.
- In den Anwendungseinstellungen, die du in Logto konfiguriert hast, unter Einstellungen → Endpoints & Credentials.
Abrufen vom OpenID Connect Discovery-Endpunkt
Diese Werte können vom OpenID Connect Discovery-Endpunkt von Logto abgerufen werden:
https://<your-logto-endpoint>/oidc/.well-known/openid-configuration
Hier ist ein Beispiel für eine Antwort (andere Felder wurden zur Übersichtlichkeit weggelassen):
{
"jwks_uri": "https://your-tenant.logto.app/oidc/jwks",
"issuer": "https://your-tenant.logto.app/oidc"
}
Im Code fest hinterlegen (nicht empfohlen)
Da Logto keine Anpassung der JWKS-URI oder des Ausstellers (Issuer) erlaubt, kannst du diese Werte fest in deinem Code hinterlegen. Dies wird jedoch für Produktionsanwendungen nicht empfohlen, da dies den Wartungsaufwand erhöhen kann, falls sich zukünftig Konfigurationen ändern.
- JWKS URI:
https://<your-logto-endpoint>/oidc/jwks
- Aussteller (Issuer):
https://<your-logto-endpoint>/oidc
Token und Berechtigungen validieren
Nach dem Extrahieren des Tokens und dem Abrufen der OIDC-Konfiguration überprüfe Folgendes:
- Signatur: JWT muss gültig und von Logto (über JWKS) signiert sein.
- Aussteller (Issuer): Muss mit dem Aussteller deines Logto-Tenants übereinstimmen.
- Zielgruppe (Audience): Muss mit dem in Logto registrierten Ressourcenindikator der API oder dem Organisationskontext (falls zutreffend) übereinstimmen.
- Ablauf (Expiration): Token darf nicht abgelaufen sein.
- Berechtigungen (Scopes): Token muss die erforderlichen Berechtigungen für deine API / Aktion enthalten. Berechtigungen sind durch Leerzeichen getrennte Zeichenfolgen im
scope
-Anspruch. - Organisationskontext: Wenn du API-Ressourcen auf Organisationsebene schützt, überprüfe den
organization_id
-Anspruch.
Siehe JSON Web Token, um mehr über die Struktur und Ansprüche von JWT zu erfahren.
Was bei jedem Berechtigungsmodell zu prüfen ist
- Globale API-Ressourcen
- Organisation (nicht-API) Berechtigungen
- API-Ressourcen auf Organisationsebene
- Audience-Anspruch (
aud
): API-Ressourcenindikator - Organisations-Anspruch (
organization_id
): Nicht vorhanden - Zu prüfende Berechtigungen (
scope
): API-Ressourcen-Berechtigungen
- Audience-Anspruch (
aud
):urn:logto:organization:<id>
(Organisationskontext ist imaud
-Anspruch) - Organisations-Anspruch (
organization_id
): Nicht vorhanden - Zu prüfende Berechtigungen (
scope
): Organisationsberechtigungen
- Audience-Anspruch (
aud
): API-Ressourcenindikator - Organisations-Anspruch (
organization_id
): Organisations-ID (muss mit Anfrage übereinstimmen) - Zu prüfende Berechtigungen (
scope
): API-Ressourcen-Berechtigungen
Für nicht-API-Organisationsberechtigungen wird der Organisationskontext durch den aud
-Anspruch
dargestellt (z. B. urn:logto:organization:abc123
). Der organization_id
-Anspruch ist nur für
Tokens von API-Ressourcen auf Organisationsebene vorhanden.
Validiere immer sowohl Berechtigungen (Scopes) als auch Kontext (Audience, Organisation) für sichere Multi-Tenant-APIs.
Validierungslogik hinzufügen
Wir verwenden jsonwebtoken, um JWTs zu validieren. Füge die benötigten Abhängigkeiten zu deiner Cargo.toml
hinzu:
[dependencies]
jsonwebtoken = "9.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1.0", features = ["full"] }
Füge zunächst diese gemeinsamen Hilfsfunktionen hinzu, um die JWT-Validierung zu behandeln:
use crate::{AuthInfo, AuthorizationError, ISSUER, JWKS_URI};
use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation};
use serde_json::Value;
use std::collections::HashMap;
pub struct JwtValidator {
jwks: HashMap<String, DecodingKey>,
}
impl JwtValidator {
pub async fn new() -> Result<Self, AuthorizationError> {
let jwks = Self::fetch_jwks().await?;
Ok(Self { jwks })
}
async fn fetch_jwks() -> Result<HashMap<String, DecodingKey>, AuthorizationError> {
let response = reqwest::get(JWKS_URI).await.map_err(|e| {
AuthorizationError::with_status(format!("Failed to fetch JWKS: {}", e), 401)
})?;
let jwks: Value = response.json().await.map_err(|e| {
AuthorizationError::with_status(format!("Failed to parse JWKS: {}", e), 401)
})?;
let mut keys = HashMap::new();
if let Some(keys_array) = jwks["keys"].as_array() {
for key in keys_array {
if let (Some(kid), Some(kty), Some(n), Some(e)) = (
key["kid"].as_str(),
key["kty"].as_str(),
key["n"].as_str(),
key["e"].as_str(),
) {
if kty == "RSA" {
if let Ok(decoding_key) = DecodingKey::from_rsa_components(n, e) {
keys.insert(kid.to_string(), decoding_key);
}
}
}
}
}
if keys.is_empty() {
return Err(AuthorizationError::with_status("No valid keys found in JWKS", 401));
}
Ok(keys)
}
pub fn validate_jwt(&self, token: &str) -> Result<AuthInfo, AuthorizationError> {
let header = decode_header(token).map_err(|e| {
AuthorizationError::with_status(format!("Invalid token header: {}", e), 401)
})?;
let kid = header.kid.ok_or_else(|| {
AuthorizationError::with_status("Token missing kid claim", 401)
})?;
let key = self.jwks.get(&kid).ok_or_else(|| {
AuthorizationError::with_status("Unknown key ID", 401)
})?;
let mut validation = Validation::new(Algorithm::RS256);
validation.set_issuer(&[ISSUER]);
validation.validate_aud = false; // Wir überprüfen die Zielgruppe manuell
let token_data = decode::<Value>(token, key, &validation).map_err(|e| {
AuthorizationError::with_status(format!("Invalid token: {}", e), 401)
})?;
let claims = token_data.claims;
self.verify_payload(&claims)?;
Ok(self.create_auth_info(claims))
}
fn verify_payload(&self, claims: &Value) -> Result<(), AuthorizationError> {
// Implementiere hier deine Verifizierungslogik basierend auf dem Berechtigungsmodell
// Dies wird im Abschnitt zu den Berechtigungsmodellen unten gezeigt
Ok(())
}
fn create_auth_info(&self, claims: Value) -> AuthInfo {
let scopes = claims["scope"]
.as_str()
.map(|s| s.split(' ').map(|s| s.to_string()).collect())
.unwrap_or_default();
let audience = match &claims["aud"] {
Value::Array(arr) => arr.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect(),
Value::String(s) => vec![s.clone()],
_ => vec![],
};
AuthInfo::new(
claims["sub"].as_str().unwrap_or_default().to_string(),
claims["client_id"].as_str().map(|s| s.to_string()),
claims["organization_id"].as_str().map(|s| s.to_string()),
scopes,
audience,
)
}
}
Implementiere anschließend das Middleware, um das Zugangstoken zu überprüfen:
use crate::{AuthInfo, AuthorizationError, extract_bearer_token};
use crate::jwt_validator::JwtValidator;
use actix_web::{
dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
web, Error, HttpMessage, HttpResponse,
};
use futures::future::{ok, Ready};
use std::sync::Arc;
pub struct JwtMiddleware {
validator: Arc<JwtValidator>,
}
impl JwtMiddleware {
pub fn new(validator: Arc<JwtValidator>) -> Self {
Self { validator }
}
}
impl<S, B> Transform<S, ServiceRequest> for JwtMiddleware
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = JwtMiddlewareService<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ok(JwtMiddlewareService {
service,
validator: self.validator.clone(),
})
}
}
pub struct JwtMiddlewareService<S> {
service: S,
validator: Arc<JwtValidator>,
}
impl<S, B> Service<ServiceRequest> for JwtMiddlewareService<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = futures::future::LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
let validator = self.validator.clone();
Box::pin(async move {
let authorization = req
.headers()
.get("authorization")
.and_then(|h| h.to_str().ok());
match extract_bearer_token(authorization)
.and_then(|token| validator.validate_jwt(token))
{
Ok(auth_info) => {
// Auth-Informationen in den Request-Extensions für generische Nutzung speichern
req.extensions_mut().insert(auth_info);
let fut = self.service.call(req);
fut.await
}
Err(e) => {
let response = HttpResponse::build(
actix_web::http::StatusCode::from_u16(e.status_code)
.unwrap_or(actix_web::http::StatusCode::FORBIDDEN),
)
.json(serde_json::json!({ "error": e.message }));
Ok(req.into_response(response))
}
}
})
}
}
Entsprechend deinem Berechtigungsmodell implementiere die passende Verifizierungslogik in JwtValidator
:
- Globale API-Ressourcen
- Organisation (nicht-API) Berechtigungen
- Organisationsbezogene API-Ressourcen
fn verify_payload(&self, claims: &Value) -> Result<(), AuthorizationError> {
// Überprüfe, ob der Audience-Claim mit deinem API-Ressourcenindikator übereinstimmt
let audiences = match &claims["aud"] {
Value::Array(arr) => arr.iter().filter_map(|v| v.as_str()).collect::<Vec<_>>(),
Value::String(s) => vec![s.as_str()],
_ => vec![],
};
if !audiences.contains(&"https://your-api-resource-indicator") {
return Err(AuthorizationError::new("Invalid audience"));
}
// Überprüfe erforderliche Berechtigungen für globale API-Ressourcen
let required_scopes = vec!["api:read", "api:write"]; // Ersetze durch deine tatsächlich benötigten Berechtigungen
let scopes = claims["scope"]
.as_str()
.map(|s| s.split(' ').collect::<Vec<_>>())
.unwrap_or_default();
for required_scope in &required_scopes {
if !scopes.contains(required_scope) {
return Err(AuthorizationError::new("Insufficient scope"));
}
}
Ok(())
}
fn verify_payload(&self, claims: &Value) -> Result<(), AuthorizationError> {
// Überprüfe, ob der Audience-Claim dem Organisationsformat entspricht
let audiences = match &claims["aud"] {
Value::Array(arr) => arr.iter().filter_map(|v| v.as_str()).collect::<Vec<_>>(),
Value::String(s) => vec![s.as_str()],
_ => vec![],
};
let has_org_audience = audiences.iter().any(|aud| aud.starts_with("urn:logto:organization:"));
if !has_org_audience {
return Err(AuthorizationError::new("Invalid audience for organization permissions"));
}
// Überprüfe, ob die Organisations-ID mit dem Kontext übereinstimmt (du musst dies ggf. aus dem Request-Kontext extrahieren)
let expected_org_id = "your-organization-id"; // Aus dem Request-Kontext extrahieren
let expected_aud = format!("urn:logto:organization:{}", expected_org_id);
if !audiences.contains(&expected_aud.as_str()) {
return Err(AuthorizationError::new("Organization ID mismatch"));
}
// Überprüfe erforderliche Organisationsberechtigungen
let required_scopes = vec!["invite:users", "manage:settings"]; // Ersetze durch deine tatsächlich benötigten Berechtigungen
let scopes = claims["scope"]
.as_str()
.map(|s| s.split(' ').collect::<Vec<_>>())
.unwrap_or_default();
for required_scope in &required_scopes {
if !scopes.contains(required_scope) {
return Err(AuthorizationError::new("Insufficient organization scope"));
}
}
Ok(())
}
fn verify_payload(&self, claims: &Value) -> Result<(), AuthorizationError> {
// Überprüfe, ob der Audience-Claim mit deinem API-Ressourcenindikator übereinstimmt
let audiences = match &claims["aud"] {
Value::Array(arr) => arr.iter().filter_map(|v| v.as_str()).collect::<Vec<_>>(),
Value::String(s) => vec![s.as_str()],
_ => vec![],
};
if !audiences.contains(&"https://your-api-resource-indicator") {
return Err(AuthorizationError::new("Invalid audience for organization-level API resources"));
}
// Überprüfe, ob die Organisations-ID mit dem Kontext übereinstimmt (du musst dies ggf. aus dem Request-Kontext extrahieren)
let expected_org_id = "your-organization-id"; // Aus dem Request-Kontext extrahieren
let org_id = claims["organization_id"].as_str().unwrap_or_default();
if expected_org_id != org_id {
return Err(AuthorizationError::new("Organization ID mismatch"));
}
// Überprüfe erforderliche Berechtigungen für organisationsbezogene API-Ressourcen
let required_scopes = vec!["api:read", "api:write"]; // Ersetze durch deine tatsächlich benötigten Berechtigungen
let scopes = claims["scope"]
.as_str()
.map(|s| s.split(' ').collect::<Vec<_>>())
.unwrap_or_default();
for required_scope in &required_scopes {
if !scopes.contains(required_scope) {
return Err(AuthorizationError::new("Insufficient organization-level API scopes"));
}
}
Ok(())
}
Middleware auf deine API anwenden
Wende nun die Middleware auf deine geschützten API-Routen an.
use actix_web::{middleware::Logger, web, App, HttpRequest, HttpServer, Result};
use serde_json::{json, Value};
use std::sync::Arc;
mod lib;
mod jwt_validator;
mod middleware as jwt_middleware;
use lib::AuthInfo;
use jwt_validator::JwtValidator;
use jwt_middleware::JwtMiddleware;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let validator = Arc::new(JwtValidator::new().await.expect("Fehler beim Initialisieren des JWT-Validators"));
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(validator.clone()))
.wrap(Logger::default())
.service(
web::scope("/api/protected")
.wrap(JwtMiddleware::new(validator.clone()))
.route("", web::get().to(protected_handler))
)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
async fn protected_handler(req: HttpRequest) -> Result<web::Json<Value>> {
// Zugriff auf Authentifizierungsinformationen aus den Erweiterungen der Anfrage
let auth = req.extensions().get::<AuthInfo>().unwrap();
Ok(web::Json(json!({ "auth": auth })))
}
Teste deine geschützte API
Zugangstokens erhalten
Von deiner Client-Anwendung: Wenn du eine Client-Integration eingerichtet hast, kann deine App Tokens automatisch erhalten. Extrahiere das Zugangstoken und verwende es in API-Anfragen.
Zum Testen mit curl / Postman:
-
Benutzertokens: Verwende die Entwicklertools deiner Client-App, um das Zugangstoken aus dem localStorage oder dem Netzwerk-Tab zu kopieren.
-
Maschine-zu-Maschine-Tokens: Verwende den Client-Credentials-Flow. Hier ein nicht-normatives Beispiel mit curl:
curl -X POST https://your-tenant.logto.app/oidc/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=your-m2m-client-id" \
-d "client_secret=your-m2m-client-secret" \
-d "resource=https://your-api-resource-indicator" \
-d "scope=api:read api:write"Möglicherweise musst du die Parameter
resource
undscope
entsprechend deiner API-Ressource und Berechtigungen anpassen; einorganization_id
-Parameter kann ebenfalls erforderlich sein, wenn deine API organisationsgebunden ist.
Möchtest du den Inhalt des Tokens inspizieren? Verwende unseren JWT Decoder, um deine JWTs zu dekodieren und zu überprüfen.
Geschützte Endpunkte testen
Gültige Token-Anfrage
curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
http://localhost:3000/api/protected
Erwartete Antwort:
{
"auth": {
"sub": "user123",
"clientId": "app456",
"organizationId": "org789",
"scopes": ["api:read", "api:write"],
"audience": ["https://your-api-resource-indicator"]
}
}
Fehlendes Token
curl http://localhost:3000/api/protected
Erwartete Antwort (401):
{
"error": "Authorization header is missing"
}
Ungültiges Token
curl -H "Authorization: Bearer invalid-token" \
http://localhost:3000/api/protected
Erwartete Antwort (401):
{
"error": "Invalid token"
}
Berechtigungsmodell-spezifisches Testen
- Globale API-Ressourcen
- Organisations-(Nicht-API)-Berechtigungen
- Organisationsgebundene API-Ressourcen
Testszenarien für APIs, die mit globalen Berechtigungen geschützt sind:
- Gültige Berechtigungen: Teste mit Tokens, die deine erforderlichen API-Berechtigungen enthalten (z. B.
api:read
,api:write
) - Fehlende Berechtigungen: Erwarte 403 Verboten, wenn das Token die erforderlichen Berechtigungen nicht enthält
- Falsche Zielgruppe: Erwarte 403 Verboten, wenn die Zielgruppe nicht mit der API-Ressource übereinstimmt
# Token mit fehlenden Berechtigungen - erwarte 403
curl -H "Authorization: Bearer token-without-required-scopes" \
http://localhost:3000/api/protected
Testszenarien für organisationsspezifische Zugangskontrolle:
- Gültiges Organisationstoken: Teste mit Tokens, die den korrekten Organisationskontext enthalten (Organisations-ID und Berechtigungen)
- Fehlende Berechtigungen: Erwarte 403 Verboten, wenn der Benutzer keine Berechtigungen für die angeforderte Aktion hat
- Falsche Organisation: Erwarte 403 Verboten, wenn die Zielgruppe nicht mit dem Organisationskontext übereinstimmt (
urn:logto:organization:<organization_id>
)
# Token für falsche Organisation - erwarte 403
curl -H "Authorization: Bearer token-for-different-organization" \
http://localhost:3000/api/protected
Testszenarien, die API-Ressourcen-Validierung mit Organisationskontext kombinieren:
- Gültige Organisation + API-Berechtigungen: Teste mit Tokens, die sowohl den Organisationskontext als auch die erforderlichen API-Berechtigungen enthalten
- Fehlende API-Berechtigungen: Erwarte 403 Verboten, wenn das Organisationstoken die erforderlichen API-Berechtigungen nicht enthält
- Falsche Organisation: Erwarte 403 Verboten, wenn auf die API mit einem Token aus einer anderen Organisation zugegriffen wird
- Falsche Zielgruppe: Erwarte 403 Verboten, wenn die Zielgruppe nicht mit der organisationsgebundenen API-Ressource übereinstimmt
# Organisationstoken ohne API-Berechtigungen - erwarte 403
curl -H "Authorization: Bearer organization-token-without-api-scopes" \
http://localhost:3000/api/protected
Weiterführende Literatur
RBAC in der Praxis: Sichere Autorisierung für deine Anwendung implementieren
Entwicklung einer Multi-Tenant-SaaS-Anwendung: Ein vollständiger Leitfaden von Design bis Implementierung