请求守卫 #

请求守卫是Rocket中实现请求级别验证和权限控制的核心机制。通过实现 FromRequest trait,可以在路由处理前进行各种检查。

FromRequest Trait #

基本定义 #

rust
use rocket::request::{self, FromRequest, Request, Outcome};
use rocket::http::Status;

#[rocket::async_trait]
impl<'r> FromRequest<'r> for MyGuard {
    type Error = MyError;

    async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
        if check_condition(request) {
            Outcome::Success(MyGuard)
        } else {
            Outcome::Error((Status::Unauthorized, MyError))
        }
    }
}

Outcome类型 #

Outcome 说明
Success(value) 验证成功,继续处理
Error((status, error)) 验证失败,返回错误
Forward(status) 转发到下一个路由

基本认证守卫 #

API Key认证 #

rust
use rocket::request::{self, FromRequest, Request, Outcome};
use rocket::http::Status;

pub struct ApiKey(pub String);

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

    async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
        match request.headers().get_one("X-API-Key") {
            Some(key) if is_valid_key(key) => {
                Outcome::Success(ApiKey(key.to_string()))
            }
            _ => Outcome::Error((Status::Unauthorized, ())),
        }
    }
}

fn is_valid_key(key: &str) -> bool {
    key == "secret-api-key"
}

#[get("/protected")]
fn protected(api_key: ApiKey) -> String {
    format!("Access granted with key: {}", api_key.0)
}

Bearer Token认证 #

rust
use rocket::request::{self, FromRequest, Request, Outcome};
use rocket::http::Status;

pub struct User {
    pub id: u32,
    pub username: String,
}

#[rocket::async_trait]
impl<'r> FromRequest<'r> for User {
    type Error = AuthError;

    async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
        let auth_header = request.headers().get_one("Authorization");
        
        match auth_header {
            Some(header) if header.starts_with("Bearer ") => {
                let token = header.trim_start_matches("Bearer ");
                match validate_token(token).await {
                    Ok(user) => Outcome::Success(user),
                    Err(e) => Outcome::Error((Status::Unauthorized, e)),
                }
            }
            _ => Outcome::Error((Status::Unauthorized, AuthError::MissingToken)),
        }
    }
}

#[derive(Debug)]
pub enum AuthError {
    MissingToken,
    InvalidToken,
}

async fn validate_token(token: &str) -> Result<User, AuthError> {
    if token == "valid-token" {
        Ok(User { id: 1, username: "alice".to_string() })
    } else {
        Err(AuthError::InvalidToken)
    }
}

#[get("/profile")]
fn profile(user: User) -> String {
    format!("Welcome, {}!", user.username)
}

角色权限守卫 #

角色定义 #

rust
use rocket::request::{self, FromRequest, Request, Outcome};
use rocket::http::Status;

#[derive(Debug, Clone, PartialEq)]
pub enum Role {
    Admin,
    Moderator,
    User,
}

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

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

    async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
        let token = request.headers()
            .get_one("Authorization")
            .and_then(|h| h.strip_prefix("Bearer "));
        
        match token {
            Some("admin-token") => Outcome::Success(AuthUser {
                id: 1,
                username: "admin".to_string(),
                role: Role::Admin,
            }),
            Some("mod-token") => Outcome::Success(AuthUser {
                id: 2,
                username: "moderator".to_string(),
                role: Role::Moderator,
            }),
            Some("user-token") => Outcome::Success(AuthUser {
                id: 3,
                username: "user".to_string(),
                role: Role::User,
            }),
            _ => Outcome::Error((Status::Unauthorized, ())),
        }
    }
}

管理员守卫 #

rust
pub struct AdminUser(pub AuthUser);

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

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

#[get("/admin/dashboard")]
fn admin_dashboard(admin: AdminUser) -> String {
    format!("Admin Dashboard - Welcome {}", admin.0.username)
}

组合守卫 #

多重验证 #

rust
use rocket::request::{self, FromRequest, Request, Outcome};
use rocket::http::Status;

pub struct Authenticated {
    pub user_id: u32,
}

pub struct RateLimited {
    pub remaining: u32,
}

#[get("/api/data")]
fn api_data(auth: Authenticated, rate: RateLimited) -> String {
    format!("User {} - {} requests remaining", auth.user_id, rate.remaining)
}

可选守卫 #

使用Option #

rust
#[get("/content")]
fn content(user: Option<AuthUser>) -> String {
    match user {
        Some(u) => format!("Private content for {}", u.username),
        None => "Public content".to_string(),
    }
}

使用Result #

rust
#[get("/profile")]
fn profile(user: Result<AuthUser, AuthError>) -> Result<String, Status> {
    user.map(|u| format!("Profile of {}", u.username))
        .map_err(|_| Status::Unauthorized)
}

请求信息守卫 #

获取客户端IP #

rust
use rocket::request::{self, FromRequest, Request, Outcome};
use std::net::IpAddr;

pub struct ClientIp(pub IpAddr);

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

    async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
        match request.client_ip() {
            Some(ip) => Outcome::Success(ClientIp(ip)),
            None => Outcome::Error((Status::BadRequest, ())),
        }
    }
}

#[get("/ip")]
fn get_ip(ip: ClientIp) -> String {
    format!("Your IP: {}", ip.0)
}

获取User-Agent #

rust
pub struct UserAgent(pub String);

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

    async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
        match request.headers().get_one("User-Agent") {
            Some(ua) => Outcome::Success(UserAgent(ua.to_string())),
            None => Outcome::Success(UserAgent("Unknown".to_string())),
        }
    }
}

完整示例 #

rust
#[macro_use] extern crate rocket;

use rocket::request::{self, FromRequest, Request, Outcome};
use rocket::http::Status;
use rocket::serde::json::Json;
use rocket::serde::{Serialize, Deserialize};

#[derive(Debug, Clone, PartialEq)]
enum Role {
    Admin,
    User,
}

#[derive(Debug)]
enum AuthError {
    MissingToken,
    InvalidToken,
    InsufficientPermissions,
}

struct AuthUser {
    id: u32,
    username: String,
    role: Role,
}

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

    async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
        let auth = request.headers().get_one("Authorization");
        
        match auth {
            Some(h) if h == "Bearer admin-token" => {
                Outcome::Success(AuthUser {
                    id: 1,
                    username: "admin".to_string(),
                    role: Role::Admin,
                })
            }
            Some(h) if h == "Bearer user-token" => {
                Outcome::Success(AuthUser {
                    id: 2,
                    username: "user".to_string(),
                    role: Role::User,
                })
            }
            Some(_) => Outcome::Error((Status::Unauthorized, AuthError::InvalidToken)),
            None => Outcome::Error((Status::Unauthorized, AuthError::MissingToken)),
        }
    }
}

struct AdminUser(AuthUser);

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

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

#[get("/public")]
fn public() -> &'static str {
    "Public content"
}

#[get("/protected")]
fn protected(user: AuthUser) -> String {
    format!("Protected content for {}", user.username)
}

#[get("/admin")]
fn admin_only(admin: AdminUser) -> String {
    format!("Admin panel - Welcome {}", admin.0.username)
}

#[get("/optional")]
fn optional_auth(user: Option<AuthUser>) -> String {
    match user {
        Some(u) => format!("Hello, {}", u.username),
        None => "Hello, guest".to_string(),
    }
}

#[launch]
fn rocket() -> _ {
    rocket::build()
        .mount("/api", routes![public, protected, admin_only, optional_auth])
}

测试示例 #

bash
# 公开访问
curl http://127.0.0.1:8000/api/public

# 受保护访问(无认证)
curl http://127.0.0.1:8000/api/protected

# 受保护访问(用户认证)
curl -H "Authorization: Bearer user-token" http://127.0.0.1:8000/api/protected

# 管理员访问(用户认证 - 失败)
curl -H "Authorization: Bearer user-token" http://127.0.0.1:8000/api/admin

# 管理员访问(管理员认证)
curl -H "Authorization: Bearer admin-token" http://127.0.0.1:8000/api/admin

下一步 #

掌握了请求守卫后,让我们继续学习 Fairings中间件,了解Rocket的中间件系统。

最后更新:2026-03-28