Einleitung
\nModerne Webentwicklung ist ohne sichere Authentifizierung und Autorisierung von Benutzern nicht denkbar. Die beiden beliebtesten Standards — OAuth 2.0 und JWT (JSON Web Tokens) — sind zu De-facto-Industriestandards geworden. Wenn Sie in Rust programmieren, haben Sie Glück: Diese Sprache bietet nicht nur höchste Leistung, sondern auch leistungsstarke Bibliotheken zur Implementierung dieser Protokolle.
\nIn diesem Artikel werden wir uns ansehen, was OAuth 2.0 und JWT sind, wie sie zusammenarbeiten, und ein einfaches, aber funktionierendes Beispiel für die Autorisierung in Rust unter Verwendung des Frameworks actix-web und der Crate jsonwebtoken schreiben. Der Artikel richtet sich an Anfänger, daher gehen wir Schritt für Schritt vor – von der Theorie bis zum Code.
Was sind OAuth 2.0 und JWT?
\nBevor wir Code schreiben, ist es wichtig, die Konzepte zu verstehen. OAuth 2.0 ist ein Autorisierungsprotokoll, das es Anwendungen ermöglicht, eingeschränkten Zugriff auf Benutzerressourcen zu erhalten, ohne das Passwort des Benutzers weiterzugeben. Es wird verwendet, wenn Sie sich über Google, GitHub oder VK auf einer Website anmelden.
\nJWT ist ein Token-Format (JSON Object Signing and Encryption), das Behauptungen (Claims) über den Benutzer enthält. Der Token wird vom Server signiert, sodass der Client ihn nicht fälschen kann. Im Kontext von OAuth 2.0 wird JWT häufig als Access Token (Zugriffstoken) verwendet.
\nEin typisches Szenario: Der Benutzer gibt Benutzername und Passwort ein, der Server überprüft sie, generiert einen JWT-Token und gibt ihn an den Client zurück. Der Client sendet diesen Token bei jeder Anfrage im Header Authorization: Bearer <token>. Der Server überprüft die Signatur und extrahiert die Benutzerdaten.
Einrichtung des Rust-Projekts
\nErstellen wir ein neues Projekt:
\ncargo new rust_oauth_jwt\ncd rust_oauth_jwt\nFügen wir die erforderlichen Abhängigkeiten in Cargo.toml hinzu:
[dependencies]\nactix-web = "4"\nactix-rt = "2"\nserde = { version = "1", features = ["derive"] }\nserde_json = "1"\njsonwebtoken = "9"\nchrono = { version = "0.4", features = ["serde"] }\ndotenv = "0.15"\nenv_logger = "0.11"\nlog = "0.4"\nErstellen wir eine Datei .env im Stammverzeichnis des Projekts, um den geheimen Schlüssel zu speichern:
JWT_SECRET=my_super_secret_key_change_in_production\nJWT_EXPIRATION_HOURS=24\nLaden wir die Umgebungsvariablen in main.rs:
use dotenv::dotenv;\nuse std::env;\n\nfn main() {\n dotenv().ok();\n env_logger::init();\n // ... restlicher Code\n}\n\nErstellung der Benutzerstruktur und des JWT-Tokens
\nDefinieren wir zunächst das Benutzermodell und die Claims-Struktur für JWT. Erstellen wir eine Datei models.rs:
use serde::{Deserialize, Serialize};\n\n#[derive(Debug, Serialize, Deserialize, Clone)]\npub struct User {\n pub id: i32,\n pub username: String,\n pub password_hash: String, // In einem echten Projekt den Hash speichern, nicht das Passwort!\n}\n\n#[derive(Debug, Serialize, Deserialize)]\npub struct Claims {\n pub sub: String, // subject (user id)\n pub username: String,\n pub exp: usize, // expiration timestamp\n}\nErstellen wir nun ein Hilfsprogramm für die Arbeit mit JWT in jwt_utils.rs:
use chrono::{Duration, Utc};\nuse jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};\nuse crate::models::Claims;\n\npub fn create_jwt(user_id: &str, username: &str) -> Result<String, jsonwebtoken::errors::Error> {\n let secret = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set");\n let expiration_hours: i64 = std::env::var("JWT_EXPIRATION_HOURS")\n .unwrap_or_else(|_| "24".to_string())\n .parse()\n .unwrap();\n\n let expiration = Utc::now()\n .checked_add_signed(Duration::hours(expiration_hours))\n .expect("valid timestamp")\n .timestamp() as usize;\n\n let claims = Claims {\n sub: user_id.to_owned(),\n username: username.to_owned(),\n