Aufbau eines sicheren Rust-Backends mit OAuth 2.0 Autorisierungscode-Flow
Grace Collins
Solutions Engineer · Leapcell

Einleitung: Absicherung Ihrer Rust-Anwendungen mit OAuth 2.0
In der heutigen vernetzten digitalen Landschaft sind Benutzerauthentifizierung und -autorisierung für jede Webanwendung von größter Bedeutung. Als Entwickler streben wir danach, sichere, skalierbare und benutzerfreundliche Systeme zu entwickeln. Wenn es darum geht, den Benutzerzugriff zu verwalten, ohne sensible Anmeldeinformationen direkt zu handhaben, sticht OAuth 2.0 als weit verbreitetes und robustes Framework hervor. Insbesondere der Autorisierungscode-Flow ist die empfohlene und sicherste Methode für Webanwendungen, da er die Exposition von Zugriffstoken minimiert.
Rust hat sich mit seinem Fokus auf Leistung, Speichersicherheit und Nebenläufigkeit zu einer überzeugenden Wahl für die Entwicklung von Backend-Diensten entwickelt. Die Integration von OAuth 2.0 in ein Rust-Backend ermöglicht es Entwicklern, die Stärken von Rust zu nutzen und gleichzeitig einen sicheren Zugriff auf geschützte Ressourcen zu bieten. Dieser Artikel führt Sie durch den Prozess der Implementierung des OAuth 2.0 Autorisierungscode-Flows in einem Rust-Backend, erklärt die zugrundeliegenden Prinzipien und demonstriert praktische Codebeispiele.
Kernkonzepte des OAuth 2.0 Autorisierungscode-Flows
Bevor wir uns mit den Implementierungsdetails befassen, wollen wir ein klares Verständnis der Schlüsselrollen und Schritte im OAuth 2.0 Autorisierungscode-Flows erlangen.
- Ressourcenbesitzer: Dies ist der Endbenutzer, dem die geschützten Ressourcen gehören (z. B. seine Fotos, Profildaten usw.) und der den Zugriff gewährt.
- Client (Ihre Rust-Anwendung): Dies ist die Anwendung, die den Zugriff auf die geschützten Ressourcen des Ressourcenbesitzers anfordert. Sie ist beim Autorisierungsserver registriert.
- Autorisierungsserver: Dieser Server authentifiziert den Ressourcenbesitzer und stellt dem Client nach Erhalt der Zustimmung des Ressourcenbesitzers Zugriffstoken aus.
- Ressourcenserver: Dieser Server hostet die geschützten Ressourcen und akzeptiert Zugriffstoken, um dem Client den Zugriff zu gewähren. Oftmals sind der Autorisierungsserver und der Ressourcenserver dieselbe Entität oder eng integriert.
Der Autorisierungscode-Flow läuft in folgenden übergeordneten Schritten ab:
- Autorisierungsanfrage: Der Client (Ihr Rust-Backend, initiiert durch das Frontend) leitet den Browser des Ressourcenbesitzers an den Autorisierungsserver weiter. Diese Anfrage enthält die Client-ID, die angeforderten Bereiche (Scopes) und eine
redirect_uri
. - Benutzerauthentifizierung und Zustimmung: Der Autorisierungsserver authentifiziert den Ressourcenbesitzer (falls noch nicht angemeldet) und fordert ihn auf, dem Client den Zugriff auf die angeforderten Bereiche zu gewähren oder zu verweigern.
- Autorisierungs-Grant (Autorisierungscode): Wenn der Ressourcenbesitzer den Zugriff gewährt, leitet der Autorisierungsserver den Browser zurück zur
redirect_uri
des Clients, einschließlich einesauthorization_code
. - Token-Anfrage: Der Client (Ihr Rust-Backend) sendet eine direkte Server-zu-Server-Anfrage an den Token-Endpunkt des Autorisierungsservers. Diese Anfrage enthält den
authorization_code
, die Client-ID, das Client-Geheimnis und dieredirect_uri
. - Token-Antwort: Der Autorisierungsserver validiert die Anfrage und gibt bei Erfolg ein
access_token
, einrefresh_token
(optional) undexpires_in
(Token-Lebensdauer) zurück. - Ressourcenzugriff: Der Client verwendet das
access_token
, um Anfragen an den Ressourcenserver für geschützte Ressourcen zu stellen.
Implementierung des OAuth 2.0 Autorisierungscode-Flows in Rust
Lassen Sie uns eine vereinfachte Implementierung mit Rust durchgehen und uns auf die Rolle des Backends bei der Handhabung des Autorisierungscodes und dessen Umtausch gegen Token konzentrieren. Wir verwenden actix-web
für unser Webframework und reqwest
für HTTP-Anfragen. Wir gehen davon aus, dass ein Frontend die anfängliche Umleitung behandelt und letztendlich den Autorisierungscode über den Browser erhält.
Einrichtung Ihres Projekts
Erstellen Sie zunächst ein neues Rust-Projekt und fügen Sie die erforderlichen Abhängigkeiten hinzu:
car gor new oauth2_backend --bin cd oauth2_backend
Fügen Sie dann diese zu Ihrer Cargo.toml
hinzu:
[dependencies] actix-web = "4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" reqwest = { version = "0.11", features = ["json", "blocking"]} # Verwenden von Blocking für Einfachheit; asynchron ist in realen Apps bevorzugt aber url = "2.2" doten v = "0.15" # Zum einfachen Verwalten von Umgebungsvariablen
Erstellen Sie eine .env
-Datei für Ihre OAuth 2.0-Anmeldeinformationen und andere Konfigurationen:
CLIENT_ID="ihre_client_id" CLIENT_SECRET="ihr_client_secret" REDIRECT_URI="http://localhost:8080/callback" AUTH_SERVER_AUTH_URL="https://example.com/oauth/authorize" # Ersetzen Sie dies durch die URL Ihres Autorisierungsservers AUTH_SERVER_TOKEN_URL="https://example.com/oauth/token" # Ersetzen Sie dies durch die URL Ihres Autorisierungsservers
Kernlogik: Handler für Callback und Token-Austausch
Unser Rust-Backend wird hauptsächlich den Endpunkt redirect_uri
behandeln, an den der Autorisierungsserver den Autorisierungscode sendet.
// src/main.rs use actix_web::{web, App, HttpResponse, HttpServer, Responder, http::header}; use serde::{Deserialize, Serialize}; use url::Url; use dotenv::dotenv; use std::env; // Konfigurationsstruktur zur Speicherung von OAuth 2.0-Clientdetails struct AppConfig { client_id: String, client_secret: String, redirect_uri: String, auth_server_auth_url: String, auth_server_token_url: String, } // Struktur zur Darstellung der eingehenden Abfrageparameter vom Autorisierungsserver #[derive(Deserialize, Debug)] struct OAuthCallbackQuery { code: String, state: Option<String>, } // Struktur zur Darstellung des Token-Anforderungs-Bodies #[derive(Serialize, Debug)] struct TokenRequest { grant_type: String, client_id: String, client_secret: String, redirect_uri: String, code: String, } // Struktur zur Darstellung der Token-Antwort vom Autorisierungsserver #[derive(Deserialize, Debug)] struct TokenResponse { access_token: String, token_type: String, expires_in: u32, refresh_token: Option<String>, scope: Option<String>, } async fn index() -> impl Responder { // In einer echten Anwendung wäre dies typischerweise eine Frontend-Umleitung, // um den OAuth-Flow zu initiieren. Zur Demonstration zeigen wir nur die Links. HttpResponse::Ok().body("Willkommen! <a href=\"/login\">Mit OAuth anmelden</a>") } async fn login(data: web::Data<AppConfig>) -> impl Responder { let mut auth_url = Url::parse(&data.auth_server_auth_url).unwrap(); auth_url.query_pairs_mut() .append_pair("client_id", &data.client_id) .append_pair("redirect_uri", &data.redirect_uri) .append_pair("response_type", "code") .append_pair("scope", "openid profile email") // Beispiel-Scopes .append_pair("state", "zufälliger_string_für_csrf-schutz"); // Entscheidend für CSRF HttpResponse::Found() .insert_header((header::LOCATION, auth_url.to_string())) .finish() } async fn oauth_callback( query: web::Query<OAuthCallbackQuery>, data: web::Data<AppConfig>, ) -> impl Responder { println!("OAuth-Callback mit Code empfangen: {:?}", query.code); // In einer echten Anwendung würden Sie hier den 'state'-Parameter zur Verhinderung von CSRF-Angriffen validieren. let token_request = TokenRequest { grant_type: "authorization_code".to_string(), client_id: data.client_id.clone(), client_secret: data.client_secret.clone(), redirect_uri: data.redirect_uri.clone(), code: query.code.clone(), }; println!("Autorisierungscode gegen Token austauschen..."); let client = reqwest::blocking::Client::new(); match client .post(&data.auth_server_token_url) .header(header::ACCEPT, "application/json") .form(&token_request) // .form für x-www-form-urlencoded verwenden .send() { Ok(response) => { if response.status().is_success() { match response.json::<TokenResponse>() { Ok(token_response) => { println!("Token erfolgreich erhalten: {:?}", token_response.access_token); // Token sicher speichern (z. B. in einer Sitzung, Datenbank) // Zu einer geschützten Ressource oder einem Dashboard umleiten HttpResponse::Ok().body(format!( "Anmeldung erfolgreich! Zugriffstoken: {}", token_response.access_token )) } Err(e) => { eprintln!("Token-Antwort konnte nicht geparst werden: {:?}", e); HttpResponse::InternalServerError().body(format!("Token-Antwort konnte nicht geparst werden: {}", e)) } } } else { let status = response.status(); let text = response.text().unwrap_or_else(|_| "N/A".to_string()); eprintln!("Token-Austausch fehlgeschlagen mit Status: {} und Body: {}", status, text); HttpResponse::InternalServerError().body(format!( "Token-Austausch fehlgeschlagen: {} - {}", status, text )) } } Err(e) => { eprintln!("HTTP-Anfrage zum Token-Austausch fehlgeschlagen: {:?}", e); HttpResponse::InternalServerError().body(format!("HTTP-Anfrage zum Token-Austausch fehlgeschlagen: {}", e)) } } } #[actix_web::main] async fn main() -> std::io::Result<()> { dotenv().ok(); // Umgebungsvariablen aus der .env-Datei laden let config = AppConfig { client_id: env::var("CLIENT_ID").expect("CLIENT_ID nicht gesetzt"), client_secret: env::var("CLIENT_SECRET").expect("CLIENT_SECRET nicht gesetzt"), redirect_uri: env::var("REDIRECT_URI").expect("REDIRECT_URI nicht gesetzt"), auth_server_auth_url: env::var("AUTH_SERVER_AUTH_URL").expect("AUTH_SERVER_AUTH_URL nicht gesetzt"), auth_server_token_url: env::var("AUTH_SERVER_TOKEN_URL").expect("AUTH_SERVER_TOKEN_URL nicht gesetzt"), }; println!("Server läuft auf http://127.0.0.1:8080"); HttpServer::new(move || { App::new() .app_data(web::Data::new(config.clone())) // App-Konfiguration teilen .route("/", web::get().to(index)) .route("/login", web::get().to(login)) .route("/callback", web::get().to(oauth_callback)) }) .bind(("127.0.0.1", 8080))? .run() .await }
Erklärung des Codes
AppConfig
: Eine Struktur zur Speicherung Ihrer OAuth 2.0-Client-Anmeldeinformationen, geladen aus Umgebungsvariablen.index
undlogin
Handler: Dieindex
-Route ist ein Platzhalter. Dielogin
-Route zeigt, wie Ihr Backend (oder typischerweise Ihr Frontend) die Autorisierungs-URL konstruiert und den Browser des Benutzers an den Autorisierungsserver weiterleitet.oauth_callback
Handler:- Dies ist der Kern unserer Backend-OAuth-Implementierung. Es ist die
redirect_uri
, an die der Autorisierungsserver den Autorisierungscode sendet, nachdem der Benutzer die Zustimmung erteilt hat. - Es deserialisiert den
code
aus den Abfrageparametern mithilfe vonweb::Query
. - Eine
TokenRequest
-Struktur wird erstellt, die alle notwendigen Parameter für den Austausch des Autorisierungscodes gegen ein Zugriffstoken enthält. Beachten Sie, dass dasclient_secret
direkt von Ihrem sicheren Backend gesendet wird, niemals von der Clientseite. reqwest::blocking::Client
(der Einfachheit halber in diesem Beispiel) wird verwendet, um eine POST-Anfrage an den Token-Endpunkt des Autorisierungsservers zu senden.- Die
TokenResponse
wird deserialisiert. Wenn erfolgreich, haben Sie dasaccess_token
und möglicherweise einrefresh_token
erhalten. - **Entscheidende nächste Schritte (nicht in diesem einfachen Beispiel enthalten):
- Überprüfung des Status-Parameters: Validieren Sie immer den
state
-Parameter, um CSRF-Angriffe zu verhindern. Generieren Sie eine zufälligestate
auf Ihrem Server und speichern Sie sie (z. B. in einer Sitzung), bevor Sie zum Autorisierungsserver weiterleiten. Wenn der Callback empfangen wird, vergleichen Sie die eingehendestate
mit der gespeicherten. - Token-Speicherung: Speichern Sie das
access_token
sicher (z. B. in einem verschlüsselten Sitzungs-Cookie oder in einer Datenbank, die mit dem Benutzer verknüpft ist). - Verwendung von
refresh_token
: Wenn einrefresh_token
bereitgestellt wird, speichern Sie ihn sicher. Verwenden Sie ihn, um neueaccess_token
s zu erhalten, wenn der aktuelle abläuft, ohne dass der Benutzer sich erneut authentifizieren muss. - Benutzerinformationen: Oftmals machen Sie nach Erhalt eines Zugriffstokens eine zusätzliche Anfrage an den Benutzerinfo-Endpunkt des Autorisierungsservers (oder eine andere geschützte Ressource), um grundlegende Benutzerdetails abzurufen (z. B.
openid profile
-Bereiche).
- Überprüfung des Status-Parameters: Validieren Sie immer den
- Dies ist der Kern unserer Backend-OAuth-Implementierung. Es ist die
main
-Funktion:- Lädt Umgebungsvariablen mithilfe von
dotenv
. - Initialisiert
AppConfig
. - Konfiguriert
actix-web
-Routen und teilt dieAppConfig
über Handler hinweg mithilfe vonweb::Data
.
- Lädt Umgebungsvariablen mithilfe von
Anwendungsfälle
Diese Implementierung bietet die Grundlage für mehrere gängige Muster:
- Single Sign-On (SSO): Integration mit Identitätsanbietern wie Google, GitHub oder Okta, damit sich Benutzer mit ihren bestehenden Konten anmelden können.
- Integrationen von Drittanbietern: Ermöglichen Sie Ihrer Anwendung, mit Zustimmung des Benutzers auf Benutzerdaten von anderen Diensten zuzugreifen, z. B. das Abrufen ihres Kalenders von Google Kalender oder Beiträge von einer Social-Media-Plattform.
- API-Authentifizierung: Sichern Sie Ihre eigenen APIs, indem Sie Zugriffstoken an Ihre Client-Anwendungen ausgeben und sicherstellen, dass nur autorisierte Clients auf geschützte Backend-Ressourcen zugreifen können.
Fazit: Robuste Authentifizierung für Ihr Rust-Backend
Die Implementierung des OAuth 2.0 Autorisierungscode-Flows in Ihrem Rust-Backend bietet eine sichere, flexible und branchenübliche Methode zur Verwaltung von Benutzerauthentifizierung und -autorisierung. Durch die sorgfältige Handhabung des Austauschs von Autorisierungscodes gegen Zugriffstoken auf der Serverseite schützen Sie sensible Anmeldeinformationen und stellen sicher, dass Ihre Anwendung sicher auf geschützte Ressourcen im Namen Ihrer Benutzer zugreifen kann. Dieser robuste Ansatz, kombiniert mit den inhärenten Sicherheitsfunktionen von Rust, legt eine starke Grundlage für die Entwicklung sicherer und hochperformanter Webdienste.