身份验证 #
身份验证是Web应用安全的核心。本节将介绍如何在Rocket中实现安全的用户认证系统。
密码哈希 #
使用argon2 #
toml
[dependencies]
argon2 = "0.5"
rand = "0.8"
密码哈希实现 #
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);
let argon2 = Argon2::default();
argon2
.hash_password(password.as_bytes(), &salt)
.map(|hash| hash.to_string())
.map_err(|e| e.to_string())
}
pub fn verify_password(password: &str, hash: &str) -> bool {
let parsed_hash = match PasswordHash::new(hash) {
Ok(h) => h,
Err(_) => return false,
};
Argon2::default()
.verify_password(password.as_bytes(), &parsed_hash)
.is_ok()
}
JWT认证 #
添加依赖 #
toml
[dependencies]
jsonwebtoken = "9.2"
serde = { version = "1.0", features = ["derive"] }
JWT配置 #
rust
use serde::{Serialize, Deserialize};
use jsonwebtoken::{encode, decode, Header, Algorithm, Validation, EncodingKey, DecodingKey};
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
pub sub: String,
pub username: String,
pub exp: usize,
pub iat: usize,
}
pub struct JwtConfig {
pub secret: String,
pub expiration: u64,
}
impl JwtConfig {
pub fn new(secret: String, expiration_hours: u64) -> Self {
Self {
secret,
expiration: expiration_hours * 3600,
}
}
pub fn generate_token(&self, user_id: u32, username: &str) -> Result<String, String> {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as usize;
let claims = Claims {
sub: user_id.to_string(),
username: username.to_string(),
exp: now + self.expiration as usize,
iat: now,
};
encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(self.secret.as_bytes()),
).map_err(|e| e.to_string())
}
pub fn validate_token(&self, token: &str) -> Result<Claims, String> {
decode::<Claims>(
token,
&DecodingKey::from_secret(self.secret.as_bytes()),
&Validation::new(Algorithm::HS256),
)
.map(|data| data.claims)
.map_err(|e| e.to_string())
}
}
JWT守卫 #
rust
use rocket::request::{self, FromRequest, Request, Outcome};
use rocket::http::Status;
pub struct AuthUser {
pub user_id: u32,
pub username: String,
}
#[rocket::async_trait]
impl<'r> FromRequest<'r> for AuthUser {
type Error = AuthError;
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let config = request.guard::<&rocket::State<JwtConfig>>()
.await
.unwrap();
let auth_header = request.headers()
.get_one("Authorization")
.and_then(|h| h.strip_prefix("Bearer "));
match auth_header {
Some(token) => {
match config.validate_token(token) {
Ok(claims) => Outcome::Success(AuthUser {
user_id: claims.sub.parse().unwrap_or(0),
username: claims.username,
}),
Err(_) => Outcome::Error((Status::Unauthorized, AuthError::InvalidToken)),
}
}
None => Outcome::Error((Status::Unauthorized, AuthError::MissingToken)),
}
}
}
#[derive(Debug)]
pub enum AuthError {
MissingToken,
InvalidToken,
}
使用JWT认证 #
rust
#[post("/login", format = "json", data = "<credentials>")]
async fn login(
credentials: Json<LoginRequest>,
config: &State<JwtConfig>,
db: &State<Db>,
) -> Result<Json<LoginResponse>, Status> {
let user = db.find_user_by_email(&credentials.email)
.await
.map_err(|_| Status::Unauthorized)?;
if !verify_password(&credentials.password, &user.password_hash) {
return Err(Status::Unauthorized);
}
let token = config.generate_token(user.id, &user.username)
.map_err(|_| Status::InternalServerError)?;
Ok(Json(LoginResponse {
token,
user: UserInfo {
id: user.id,
username: user.username,
email: user.email,
},
}))
}
#[get("/profile")]
fn profile(user: AuthUser) -> Json<UserInfo> {
Json(UserInfo {
id: user.user_id,
username: user.username,
email: "user@example.com".to_string(),
})
}
OAuth集成 #
OAuth流程 #
text
1. 用户点击登录 → 重定向到OAuth提供商
2. 用户授权 → 回调到应用
3. 获取access_token → 获取用户信息
4. 创建会话 → 返回应用
GitHub OAuth示例 #
rust
use rocket::response::Redirect;
use serde::Deserialize;
#[derive(Deserialize)]
struct GitHubToken {
access_token: String,
}
#[derive(Deserialize)]
struct GitHubUser {
login: String,
id: u64,
email: Option<String>,
}
#[get("/auth/github")]
fn github_login() -> Redirect {
let client_id = "your-github-client-id";
let redirect_uri = "http://localhost:8000/auth/github/callback";
let url = format!(
"https://github.com/login/oauth/authorize?client_id={}&redirect_uri={}&scope=user:email",
client_id, redirect_uri
);
Redirect::to(url)
}
#[get("/auth/github/callback?<code>")]
async fn github_callback(code: &str) -> Result<Redirect, Status> {
let client = reqwest::Client::new();
let token_response = client
.post("https://github.com/login/oauth/access_token")
.query(&[
("client_id", "your-github-client-id"),
("client_secret", "your-github-client-secret"),
("code", code),
])
.header("Accept", "application/json")
.send()
.await
.map_err(|_| Status::InternalServerError)?
.json::<GitHubToken>()
.await
.map_err(|_| Status::InternalServerError)?;
let user = client
.get("https://api.github.com/user")
.header("Authorization", format!("token {}", token_response.access_token))
.header("User-Agent", "Rocket-App")
.send()
.await
.map_err(|_| Status::InternalServerError)?
.json::<GitHubUser>()
.await
.map_err(|_| Status::InternalServerError)?;
println!("GitHub user: {:?}", user);
Ok(Redirect::to("/"))
}
完整示例 #
rust
#[macro_use] extern crate rocket;
use rocket::serde::json::Json;
use rocket::serde::{Serialize, Deserialize};
use rocket::State;
use rocket::http::Status;
use rocket::request::{self, FromRequest, Request, Outcome};
#[derive(Serialize, Deserialize)]
struct Claims {
sub: String,
username: String,
exp: usize,
}
struct JwtConfig {
secret: String,
}
impl JwtConfig {
fn generate(&self, user_id: u32, username: &str) -> String {
let exp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as usize + 3600;
let claims = Claims {
sub: user_id.to_string(),
username: username.to_string(),
exp,
};
jsonwebtoken::encode(
&jsonwebtoken::Header::default(),
&claims,
&jsonwebtoken::EncodingKey::from_secret(self.secret.as_bytes()),
).unwrap()
}
fn validate(&self, token: &str) -> Option<Claims> {
jsonwebtoken::decode::<Claims>(
token,
&jsonwebtoken::DecodingKey::from_secret(self.secret.as_bytes()),
&jsonwebtoken::Validation::default(),
).ok().map(|d| d.claims)
}
}
struct AuthUser {
user_id: u32,
username: String,
}
#[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::<&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)) {
Some(claims) => Outcome::Success(AuthUser {
user_id: claims.sub.parse().unwrap(),
username: claims.username,
}),
None => Outcome::Error((Status::Unauthorized, ())),
}
}
}
#[derive(Deserialize)]
struct LoginRequest {
username: String,
password: String,
}
#[derive(Serialize)]
struct LoginResponse {
token: String,
}
#[post("/login", format = "json", data = "<req>")]
fn login(req: Json<LoginRequest>, config: &State<JwtConfig>) -> Json<LoginResponse> {
if req.username == "admin" && req.password == "password" {
let token = config.generate(1, &req.username);
Json(LoginResponse { token })
} else {
Json(LoginResponse { token: String::new() })
}
}
#[get("/profile")]
fn profile(user: AuthUser) -> String {
format!("Welcome, {} (ID: {})", user.username, user.user_id)
}
#[launch]
fn rocket() -> _ {
rocket::build()
.manage(JwtConfig { secret: "secret".to_string() })
.mount("/api", routes![login, profile])
}
下一步 #
掌握了身份验证后,让我们继续学习 CSRF防护,了解如何防止跨站请求伪造攻击。
最后更新:2026-03-28