安全最佳实践 #
Web应用安全是一个持续的过程。本节将介绍Rocket应用开发中的安全最佳实践。
输入验证 #
类型安全验证 #
rust
use rocket::form::{self, FromForm};
#[derive(FromForm)]
struct UserInput {
#[field(validate = len(3..=50))]
username: String,
#[field(validate = len(8..))]
password: String,
#[field(validate = contains('@'))]
email: String,
#[field(validate = range(0..=150))]
age: u8,
}
自定义验证器 #
rust
fn validate_username(username: &str) -> form::Result<'_, ()> {
if username.len() < 3 {
return Err(form::Error::validation("Username too short").into());
}
if !username.chars().all(|c| c.is_alphanumeric() || c == '_') {
return Err(form::Error::validation("Invalid characters").into());
}
Ok(())
}
#[derive(FromForm)]
struct RegisterForm {
#[field(validate = validate_username)]
username: String,
}
SQL注入防护 #
rust
use sqlx::query_as;
#[get("/users/<id>")]
async fn get_user(mut db: Connection<Db>, id: i32) -> Option<Json<User>> {
query_as!(
User,
"SELECT * FROM users WHERE id = $1",
id
)
.fetch_optional(&mut **db)
.await
.ok()
.flatten()
.map(Json)
}
安全头部 #
安全头部Fairing #
rust
use rocket::{Request, Response};
use rocket::fairing::{Fairing, Info, Kind};
use rocket::http::Header;
pub struct SecurityHeaders;
#[rocket::async_trait]
impl Fairing for SecurityHeaders {
fn info(&self) -> Info {
Info {
name: "Security Headers",
kind: Kind::Response,
}
}
async fn on_response<'r>(&self, _request: &'r Request<'_>, response: &mut Response<'r>) {
response.set_header(Header::new("X-Content-Type-Options", "nosniff"));
response.set_header(Header::new("X-Frame-Options", "DENY"));
response.set_header(Header::new("X-XSS-Protection", "1; mode=block"));
response.set_header(Header::new("Strict-Transport-Security", "max-age=31536000; includeSubDomains"));
response.set_header(Header::new("Content-Security-Policy", "default-src 'self'"));
response.set_header(Header::new("Referrer-Policy", "strict-origin-when-cross-origin"));
}
}
安全头部说明 #
| 头部 | 作用 |
|---|---|
| X-Content-Type-Options | 防止MIME类型嗅探 |
| X-Frame-Options | 防止点击劫持 |
| X-XSS-Protection | XSS过滤器 |
| Strict-Transport-Security | 强制HTTPS |
| Content-Security-Policy | 内容安全策略 |
| Referrer-Policy | 控制Referrer信息 |
密码安全 #
密码哈希 #
rust
use argon2::{
password_hash::{rand_core::OsRng, PasswordHasher, 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())
}
密码强度验证 #
rust
fn validate_password_strength(password: &str) -> Result<(), String> {
if password.len() < 8 {
return Err("Password must be at least 8 characters".to_string());
}
if !password.chars().any(|c| c.is_uppercase()) {
return Err("Password must contain uppercase letter".to_string());
}
if !password.chars().any(|c| c.is_lowercase()) {
return Err("Password must contain lowercase letter".to_string());
}
if !password.chars().any(|c| c.is_numeric()) {
return Err("Password must contain number".to_string());
}
Ok(())
}
会话安全 #
安全Cookie配置 #
rust
use rocket::http::{Cookie, SameSite};
let cookie = Cookie::build(("session_id", session_id))
.http_only(true)
.secure(true)
.same_site(SameSite::Strict)
.path("/")
.max_age(rocket::time::Duration::hours(1))
.finish();
会话过期 #
rust
use std::time::{Duration, Instant};
struct Session {
id: String,
user_id: u32,
created_at: Instant,
expires_in: Duration,
}
impl Session {
fn is_expired(&self) -> bool {
self.created_at.elapsed() > self.expires_in
}
}
错误处理 #
不泄露敏感信息 #
rust
#[get("/user/<id>")]
async fn get_user(id: i32, db: &State<Db>) -> Result<Json<User>, Status> {
db.find_user(id)
.await
.map(Json)
.map_err(|_| Status::NotFound)
}
统一错误响应 #
rust
#[derive(Serialize)]
struct ErrorResponse {
error: String,
code: u16,
}
#[catch(404)]
fn not_found() -> Json<ErrorResponse> {
Json(ErrorResponse {
error: "Resource not found".to_string(),
code: 404,
})
}
#[catch(500)]
fn internal_error() -> Json<ErrorResponse> {
Json(ErrorResponse {
error: "Internal server error".to_string(),
code: 500,
})
}
速率限制 #
简单速率限制 #
rust
use std::collections::HashMap;
use std::sync::Mutex;
use std::time::{Duration, Instant};
pub struct RateLimiter {
requests: Mutex<HashMap<String, Vec<Instant>>>,
max_requests: usize,
window: Duration,
}
impl RateLimiter {
pub fn new(max_requests: usize, window_secs: u64) -> Self {
Self {
requests: Mutex::new(HashMap::new()),
max_requests,
window: Duration::from_secs(window_secs),
}
}
pub fn check(&self, key: &str) -> bool {
let mut requests = self.requests.lock().unwrap();
let now = Instant::now();
let entry = requests.entry(key.to_string()).or_default();
entry.retain(|&t| now.duration_since(t) < self.window);
if entry.len() >= self.max_requests {
false
} else {
entry.push(now);
true
}
}
}
安全配置 #
环境变量 #
rust
use std::env;
pub struct Config {
pub database_url: String,
pub jwt_secret: String,
pub is_production: bool,
}
impl Config {
pub fn from_env() -> Self {
Self {
database_url: env::var("DATABASE_URL")
.expect("DATABASE_URL must be set"),
jwt_secret: env::var("JWT_SECRET")
.expect("JWT_SECRET must be set"),
is_production: env::var("ROCKET_ENV")
.map(|e| e == "production")
.unwrap_or(false),
}
}
}
Rocket.toml安全配置 #
toml
[default]
secret_key = "your-secret-key-here"
limits = { forms = "64 kB", json = "1 MiB" }
[release]
address = "0.0.0.0"
port = 80
log_level = "critical"
安全检查清单 #
开发阶段 #
- [ ] 所有输入都经过验证
- [ ] 使用参数化查询
- [ ] 密码正确哈希存储
- [ ] 敏感信息不记录日志
部署阶段 #
- [ ] 启用HTTPS
- [ ] 设置安全头部
- [ ] 配置CORS策略
- [ ] 启用速率限制
运维阶段 #
- [ ] 定期更新依赖
- [ ] 监控异常请求
- [ ] 定期安全审计
- [ ] 备份重要数据
完整示例 #
rust
#[macro_use] extern crate rocket;
use rocket::http::{Cookie, CookieJar, SameSite, Status, Header};
use rocket::fairing::{Fairing, Info, Kind};
use rocket::serde::json::Json;
use rocket::serde::{Serialize, Deserialize};
use rocket::form::Form;
struct SecurityHeaders;
#[rocket::async_trait]
impl Fairing for SecurityHeaders {
fn info(&self) -> Info {
Info { name: "Security Headers", kind: Kind::Response }
}
async fn on_response<'r>(&self, _req: &'r rocket::Request<'_>, res: &mut rocket::Response<'r>) {
res.set_header(Header::new("X-Content-Type-Options", "nosniff"));
res.set_header(Header::new("X-Frame-Options", "DENY"));
}
}
#[derive(FromForm)]
struct LoginForm {
username: String,
password: String,
}
#[derive(Serialize)]
struct User {
id: u32,
username: String,
}
#[post("/login", data = "<form>")]
fn login(form: Form<LoginForm>, jar: &CookieJar<'_>) -> Result<Json<User>, Status> {
if form.username == "admin" && form.password == "password" {
jar.add(
Cookie::build(("session", "valid-session"))
.http_only(true)
.secure(true)
.same_site(SameSite::Strict)
.finish()
);
Ok(Json(User { id: 1, username: form.username.clone() }))
} else {
Err(Status::Unauthorized)
}
}
#[get("/profile")]
fn profile(jar: &CookieJar<'_>) -> Result<Json<User>, Status> {
match jar.get("session") {
Some(c) if c.value() == "valid-session" => {
Ok(Json(User { id: 1, username: "admin".to_string() }))
}
_ => Err(Status::Unauthorized),
}
}
#[launch]
fn rocket() -> _ {
rocket::build()
.attach(SecurityHeaders)
.mount("/api", routes![login, profile])
}
下一步 #
掌握了安全最佳实践后,让我们继续学习 错误处理,了解如何优雅地处理应用错误。
最后更新:2026-03-28