用户认证系统 #

本节将实现一个完整的用户认证系统,包括注册、登录、JWT认证和权限控制。

项目结构 #

text
auth_system/
├── Cargo.toml
├── Rocket.toml
├── src/
│   ├── main.rs
│   ├── auth/
│   │   ├── mod.rs
│   │   ├── jwt.rs
│   │   └── password.rs
│   ├── guards/
│   │   └── auth.rs
│   ├── models/
│   │   └── user.rs
│   └── routes/
│       └── auth.rs

依赖配置 #

toml
[dependencies]
rocket = { version = "0.5", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
jsonwebtoken = "9.2"
argon2 = "0.5"
rand = "0.8"
uuid = { version = "1.0", features = ["v4", "serde"] }
chrono = { version = "0.4", features = ["serde"] }

密码哈希 #

rust
use argon2::{
    password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
    Argon2,
};

pub fn hash_password(password: &str) -> Result<String, String> {
    let salt = SaltString::generate(&mut OsRng);
    Argon2::default()
        .hash_password(password.as_bytes(), &salt)
        .map(|h| h.to_string())
        .map_err(|e| e.to_string())
}

pub fn verify_password(password: &str, hash: &str) -> bool {
    let parsed = match PasswordHash::new(hash) {
        Ok(p) => p,
        Err(_) => return false,
    };
    Argon2::default()
        .verify_password(password.as_bytes(), &parsed)
        .is_ok()
}

JWT处理 #

rust
use serde::{Serialize, Deserialize};
use jsonwebtoken::{encode, decode, Header, Validation, EncodingKey, DecodingKey};

#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
    pub sub: String,
    pub username: String,
    pub role: String,
    pub exp: usize,
}

pub struct JwtConfig {
    secret: String,
    expiration: u64,
}

impl JwtConfig {
    pub fn new(secret: String, expiration_hours: u64) -> Self {
        Self {
            secret,
            expiration: expiration_hours * 3600,
        }
    }
    
    pub fn generate(&self, user_id: &str, username: &str, role: &str) -> Result<String, String> {
        let exp = chrono::Utc::now()
            .checked_add_signed(chrono::Duration::seconds(self.expiration as i64))
            .unwrap()
            .timestamp() as usize;
        
        let claims = Claims {
            sub: user_id.to_string(),
            username: username.to_string(),
            role: role.to_string(),
            exp,
        };
        
        encode(
            &Header::default(),
            &claims,
            &EncodingKey::from_secret(self.secret.as_bytes()),
        ).map_err(|e| e.to_string())
    }
    
    pub fn validate(&self, token: &str) -> Result<Claims, String> {
        decode::<Claims>(
            token,
            &DecodingKey::from_secret(self.secret.as_bytes()),
            &Validation::default(),
        )
        .map(|d| d.claims)
        .map_err(|e| e.to_string())
    }
}

认证守卫 #

rust
use rocket::request::{self, FromRequest, Request, Outcome};
use rocket::http::Status;
use crate::auth::jwt::{JwtConfig, Claims};

pub struct AuthUser {
    pub id: String,
    pub username: String,
    pub role: String,
}

pub struct AdminUser(pub AuthUser);

#[rocket::async_trait]
impl<'r> FromRequest<'r> for AuthUser {
    type Error = ();

    async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
        let config = request.guard::<&rocket::State<JwtConfig>>()
            .await
            .unwrap();
        
        let token = request.headers()
            .get_one("Authorization")
            .and_then(|h| h.strip_prefix("Bearer "));
        
        match token.and_then(|t| config.validate(t).ok()) {
            Some(claims) => Outcome::Success(AuthUser {
                id: claims.sub,
                username: claims.username,
                role: claims.role,
            }),
            None => Outcome::Error((Status::Unauthorized, ())),
        }
    }
}

#[rocket::async_trait]
impl<'r> FromRequest<'r> for AdminUser {
    type Error = ();

    async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
        let user = AuthUser::from_request(request).await;
        
        match user {
            Outcome::Success(u) if u.role == "admin" => Outcome::Success(AdminUser(u)),
            Outcome::Success(_) => Outcome::Error((Status::Forbidden, ())),
            Outcome::Error(e) => Outcome::Error(e),
            Outcome::Forward(f) => Outcome::Forward(f),
        }
    }
}

用户模型 #

rust
use serde::{Serialize, Deserialize};
use uuid::Uuid;

#[derive(Debug, Clone, Serialize)]
pub struct User {
    pub id: Uuid,
    pub username: String,
    pub email: String,
    pub role: String,
}

#[derive(Debug, Deserialize)]
pub struct RegisterRequest {
    pub username: String,
    pub email: String,
    pub password: String,
}

#[derive(Debug, Deserialize)]
pub struct LoginRequest {
    pub username: String,
    pub password: String,
}

#[derive(Debug, Serialize)]
pub struct AuthResponse {
    pub token: String,
    pub user: User,
}

认证路由 #

rust
use rocket::serde::json::Json;
use rocket::State;
use rocket::http::Status;
use crate::models::{User, RegisterRequest, LoginRequest, AuthResponse};
use crate::auth::{jwt::JwtConfig, password::{hash_password, verify_password}};
use crate::guards::AuthUser;
use uuid::Uuid;
use std::collections::HashMap;
use std::sync::Mutex;

struct UserStore {
    users: Mutex<HashMap<String, StoredUser>>,
}

struct StoredUser {
    id: Uuid,
    username: String,
    email: String,
    password_hash: String,
    role: String,
}

#[post("/register", format = "json", data = "<req>")]
fn register(
    req: Json<RegisterRequest>,
    store: &State<UserStore>,
    jwt: &State<JwtConfig>,
) -> Result<Json<AuthResponse>, Status> {
    let password_hash = hash_password(&req.password)
        .map_err(|_| Status::InternalServerError)?;
    
    let id = Uuid::new_v4();
    let user = StoredUser {
        id,
        username: req.username.clone(),
        email: req.email.clone(),
        password_hash,
        role: "user".to_string(),
    };
    
    let token = jwt.generate(&id.to_string(), &user.username, &user.role)
        .map_err(|_| Status::InternalServerError)?;
    
    store.users.lock().unwrap().insert(user.username.clone(), user.clone());
    
    Ok(Json(AuthResponse {
        token,
        user: User {
            id: user.id,
            username: user.username,
            email: user.email,
            role: user.role,
        },
    }))
}

#[post("/login", format = "json", data = "<req>")]
fn login(
    req: Json<LoginRequest>,
    store: &State<UserStore>,
    jwt: &State<JwtConfig>,
) -> Result<Json<AuthResponse>, Status> {
    let users = store.users.lock().unwrap();
    let user = users.get(&req.username)
        .ok_or(Status::Unauthorized)?;
    
    if !verify_password(&req.password, &user.password_hash) {
        return Err(Status::Unauthorized);
    }
    
    let token = jwt.generate(&user.id.to_string(), &user.username, &user.role)
        .map_err(|_| Status::InternalServerError)?;
    
    Ok(Json(AuthResponse {
        token,
        user: User {
            id: user.id,
            username: user.username.clone(),
            email: user.email.clone(),
            role: user.role.clone(),
        },
    }))
}

#[get("/profile")]
fn profile(user: AuthUser) -> Json<User> {
    Json(User {
        id: Uuid::parse_str(&user.id).unwrap(),
        username: user.username,
        email: "user@example.com".to_string(),
        role: user.role,
    })
}

#[get("/admin")]
fn admin_panel(_admin: crate::guards::AdminUser) -> &'static str {
    "Admin Panel"
}

主程序 #

rust
#[macro_use] extern crate rocket;

mod auth;
mod guards;
mod models;
mod routes;

use auth::jwt::JwtConfig;
use std::collections::HashMap;
use std::sync::Mutex;

struct UserStore {
    users: Mutex<HashMap<String, StoredUser>>,
}

struct StoredUser {
    id: uuid::Uuid,
    username: String,
    email: String,
    password_hash: String,
    role: String,
}

#[launch]
fn rocket() -> _ {
    rocket::build()
        .manage(JwtConfig::new("secret-key".to_string(), 24))
        .manage(UserStore {
            users: Mutex::new(HashMap::new()),
        })
        .mount("/api/auth", routes![
            routes::auth::register,
            routes::auth::login,
            routes::auth::profile,
            routes::auth::admin_panel
        ])
}

API测试 #

bash
# 注册
curl -X POST http://localhost:8000/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","email":"alice@example.com","password":"password123"}'

# 登录
curl -X POST http://localhost:8000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"password123"}'

# 获取个人信息
curl http://localhost:8000/api/auth/profile \
  -H "Authorization: Bearer {token}"

下一步 #

掌握了用户认证后,让我们继续学习 博客系统,实现一个完整的博客应用。

最后更新:2026-03-28