salvo-cors

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Salvo CORS Configuration

Salvo CORS 配置

This skill helps configure CORS and security headers in Salvo applications.
本技能可帮助你在Salvo应用中配置CORS和安全头。

Understanding CORS

了解CORS

CORS (Cross-Origin Resource Sharing) is a browser security feature that restricts web pages from making requests to different domains. Without proper CORS configuration, browsers will block cross-origin requests to your API.
CORS(跨源资源共享)是浏览器的一种安全功能,用于限制网页向不同域发起请求。如果没有正确配置CORS,浏览器会阻止对你的API的跨源请求。

Setup

设置步骤

toml
[dependencies]
salvo = { version = "0.89.0", features = ["cors"] }
toml
[dependencies]
salvo = { version = "0.89.0", features = ["cors"] }

Basic CORS Configuration

基础CORS配置

rust
use salvo::cors::Cors;
use salvo::prelude::*;

#[tokio::main]
async fn main() {
    let cors = Cors::new()
        .allow_origin("https://example.com")
        .allow_methods(vec!["GET", "POST", "PUT", "DELETE"])
        .allow_headers(vec!["Content-Type", "Authorization"])
        .into_handler();

    let router = Router::new()
        .hoop(cors)
        .push(Router::with_path("api").get(api_handler));

    let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
    Server::new(acceptor).serve(router).await;
}

#[handler]
async fn api_handler() -> &'static str {
    "Hello from API"
}
rust
use salvo::cors::Cors;
use salvo::prelude::*;

#[tokio::main]
async fn main() {
    let cors = Cors::new()
        .allow_origin("https://example.com")
        .allow_methods(vec!["GET", "POST", "PUT", "DELETE"])
        .allow_headers(vec!["Content-Type", "Authorization"])
        .into_handler();

    let router = Router::new()
        .hoop(cors)
        .push(Router::with_path("api").get(api_handler));

    let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
    Server::new(acceptor).serve(router).await;
}

#[handler]
async fn api_handler() -> &'static str {
    "Hello from API"
}

Allow All Origins (Development)

允许所有源(开发环境)

rust
use salvo::cors::Cors;

// WARNING: Only use in development
let cors = Cors::new()
    .allow_origin("*")
    .allow_methods(vec!["GET", "POST", "PUT", "DELETE", "OPTIONS"])
    .allow_headers(vec!["*"])
    .into_handler();
rust
use salvo::cors::Cors;

// 警告:仅在开发环境中使用
let cors = Cors::new()
    .allow_origin("*")
    .allow_methods(vec!["GET", "POST", "PUT", "DELETE", "OPTIONS"])
    .allow_headers(vec!["*"])
    .into_handler();

Production CORS Configuration

生产环境CORS配置

rust
use salvo::cors::Cors;
use salvo::http::Method;

let cors = Cors::new()
    // Specific allowed origins
    .allow_origin(["https://app.example.com", "https://admin.example.com"])
    // Allowed HTTP methods
    .allow_methods(vec![Method::GET, Method::POST, Method::PUT, Method::DELETE])
    // Allowed request headers
    .allow_headers(vec!["Authorization", "Content-Type", "X-Requested-With"])
    // Allow credentials (cookies, auth headers)
    .allow_credentials(true)
    // Cache preflight requests for 1 hour
    .max_age(3600)
    .into_handler();
rust
use salvo::cors::Cors;
use salvo::http::Method;

let cors = Cors::new()
    // 允许的特定源
    .allow_origin(["https://app.example.com", "https://admin.example.com"])
    // 允许的HTTP方法
    .allow_methods(vec![Method::GET, Method::POST, Method::PUT, Method::DELETE])
    // 允许的请求头
    .allow_headers(vec!["Authorization", "Content-Type", "X-Requested-With"])
    // 允许凭证(Cookie、认证头)
    .allow_credentials(true)
    // 预检请求缓存1小时
    .max_age(3600)
    .into_handler();

Permissive CORS

宽松模式CORS

Use the built-in permissive preset for quick setup:
rust
use salvo::cors::Cors;

let cors = Cors::permissive();

let router = Router::new()
    .hoop(cors)
    .get(handler);
使用内置的宽松预设快速配置:
rust
use salvo::cors::Cors;

let cors = Cors::permissive();

let router = Router::new()
    .hoop(cors)
    .get(handler);

CORS with Specific Routes

为特定路由配置CORS

Apply CORS only to API routes:
rust
let cors = Cors::new()
    .allow_origin("https://app.example.com")
    .allow_methods(vec!["GET", "POST"])
    .into_handler();

let router = Router::new()
    .push(
        Router::with_path("api")
            .hoop(cors)  // CORS only for /api routes
            .push(Router::with_path("users").get(list_users))
            .push(Router::with_path("posts").get(list_posts))
    )
    .push(
        Router::with_path("health")
            .get(health_check)  // No CORS needed
    );
仅对API路由应用CORS:
rust
let cors = Cors::new()
    .allow_origin("https://app.example.com")
    .allow_methods(vec!["GET", "POST"])
    .into_handler();

let router = Router::new()
    .push(
        Router::with_path("api")
            .hoop(cors)  // 仅对/api路由应用CORS
            .push(Router::with_path("users").get(list_users))
            .push(Router::with_path("posts").get(list_posts))
    )
    .push(
        Router::with_path("health")
            .get(health_check)  // 无需CORS
    );

Security Headers Middleware

安全头中间件

Add security headers to protect against common attacks:
rust
use salvo::prelude::*;

#[handler]
async fn security_headers(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
    // Content Security Policy - prevent XSS
    res.headers_mut().insert(
        "Content-Security-Policy",
        "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'".parse().unwrap()
    );

    // HTTP Strict Transport Security - force HTTPS
    res.headers_mut().insert(
        "Strict-Transport-Security",
        "max-age=31536000; includeSubDomains; preload".parse().unwrap()
    );

    // Prevent clickjacking
    res.headers_mut().insert(
        "X-Frame-Options",
        "DENY".parse().unwrap()
    );

    // Prevent MIME type sniffing
    res.headers_mut().insert(
        "X-Content-Type-Options",
        "nosniff".parse().unwrap()
    );

    // XSS Protection
    res.headers_mut().insert(
        "X-XSS-Protection",
        "1; mode=block".parse().unwrap()
    );

    // Referrer Policy
    res.headers_mut().insert(
        "Referrer-Policy",
        "strict-origin-when-cross-origin".parse().unwrap()
    );

    ctrl.call_next(req, depot, res).await;
}
添加安全头以防范常见攻击:
rust
use salvo::prelude::*;

#[handler]
async fn security_headers(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
    // 内容安全策略 - 防止XSS攻击
    res.headers_mut().insert(
        "Content-Security-Policy",
        "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'".parse().unwrap()
    );

    // HTTP严格传输安全 - 强制使用HTTPS
    res.headers_mut().insert(
        "Strict-Transport-Security",
        "max-age=31536000; includeSubDomains; preload".parse().unwrap()
    );

    // 防止点击劫持
    res.headers_mut().insert(
        "X-Frame-Options",
        "DENY".parse().unwrap()
    );

    // 防止MIME类型嗅探
    res.headers_mut().insert(
        "X-Content-Type-Options",
        "nosniff".parse().unwrap()
    );

    // XSS保护
    res.headers_mut().insert(
        "X-XSS-Protection",
        "1; mode=block".parse().unwrap()
    );

    // 引用策略
    res.headers_mut().insert(
        "Referrer-Policy",
        "strict-origin-when-cross-origin".parse().unwrap()
    );

    ctrl.call_next(req, depot, res).await;
}

Complete Example with CORS and Security Headers

包含CORS和安全头的完整示例

rust
use salvo::cors::Cors;
use salvo::http::Method;
use salvo::prelude::*;

#[handler]
async fn security_headers(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
    res.headers_mut().insert("X-Content-Type-Options", "nosniff".parse().unwrap());
    res.headers_mut().insert("X-Frame-Options", "DENY".parse().unwrap());
    res.headers_mut().insert("X-XSS-Protection", "1; mode=block".parse().unwrap());
    ctrl.call_next(req, depot, res).await;
}

#[handler]
async fn api_handler() -> Json<serde_json::Value> {
    Json(serde_json::json!({"status": "ok"}))
}

#[tokio::main]
async fn main() {
    // CORS configuration
    let cors = Cors::new()
        .allow_origin(["https://app.example.com"])
        .allow_methods(vec![Method::GET, Method::POST, Method::PUT, Method::DELETE])
        .allow_headers(vec!["Authorization", "Content-Type"])
        .allow_credentials(true)
        .max_age(86400)
        .into_handler();

    let router = Router::new()
        .hoop(security_headers)  // Apply security headers globally
        .hoop(cors)               // Apply CORS globally
        .push(Router::with_path("api").get(api_handler));

    let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
    Server::new(acceptor).serve(router).await;
}
rust
use salvo::cors::Cors;
use salvo::http::Method;
use salvo::prelude::*;

#[handler]
async fn security_headers(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
    res.headers_mut().insert("X-Content-Type-Options", "nosniff".parse().unwrap());
    res.headers_mut().insert("X-Frame-Options", "DENY".parse().unwrap());
    res.headers_mut().insert("X-XSS-Protection", "1; mode=block".parse().unwrap());
    ctrl.call_next(req, depot, res).await;
}

#[handler]
async fn api_handler() -> Json<serde_json::Value> {
    Json(serde_json::json!({"status": "ok"}))
}

#[tokio::main]
async fn main() {
    // CORS配置
    let cors = Cors::new()
        .allow_origin(["https://app.example.com"])
        .allow_methods(vec![Method::GET, Method::POST, Method::PUT, Method::DELETE])
        .allow_headers(vec!["Authorization", "Content-Type"])
        .allow_credentials(true)
        .max_age(86400)
        .into_handler();

    let router = Router::new()
        .hoop(security_headers)  // 全局应用安全头
        .hoop(cors)               // 全局应用CORS
        .push(Router::with_path("api").get(api_handler));

    let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
    Server::new(acceptor).serve(router).await;
}

Dynamic CORS Origins

动态CORS源

Allow origins based on request:
rust
use salvo::cors::Cors;
use salvo::prelude::*;

fn create_cors() -> Cors {
    Cors::new()
        .allow_origin(|origin: &str, _req: &Request| {
            // Allow any subdomain of example.com
            origin.ends_with(".example.com") || origin == "https://example.com"
        })
        .allow_methods(vec!["GET", "POST"])
        .allow_credentials(true)
}
根据请求允许不同的源:
rust
use salvo::cors::Cors;
use salvo::prelude::*;

fn create_cors() -> Cors {
    Cors::new()
        .allow_origin(|origin: &str, _req: &Request| {
            // 允许example.com的所有子域
            origin.ends_with(".example.com") || origin == "https://example.com"
        })
        .allow_methods(vec!["GET", "POST"])
        .allow_credentials(true)
}

CORS Configuration Options

CORS配置选项

OptionDescription
allow_origin()
Origins allowed to make requests
allow_methods()
HTTP methods allowed
allow_headers()
Request headers allowed
expose_headers()
Response headers exposed to browser
allow_credentials()
Allow cookies and auth headers
max_age()
Cache preflight response (seconds)
选项描述
allow_origin()
允许发起请求的源
allow_methods()
允许的HTTP方法
allow_headers()
允许的请求头
expose_headers()
暴露给浏览器的响应头
allow_credentials()
允许Cookie和认证头
max_age()
预检响应缓存时间(秒)

Troubleshooting CORS Issues

CORS问题排查

Common Problems

常见问题

  1. Missing OPTIONS handler: CORS preflight uses OPTIONS method
  2. Credentials with wildcard origin: Cannot use
    *
    with
    allow_credentials(true)
  3. Missing headers: Ensure all required headers are in
    allow_headers()
  1. 缺失OPTIONS处理器:CORS预检请求使用OPTIONS方法
  2. 凭证与通配符源冲突
    allow_credentials(true)
    不能与
    *
    源同时使用
  3. 缺失必要头:确保所有需要的头都在
    allow_headers()
    中配置

Debug CORS

调试CORS

rust
#[handler]
async fn cors_debug(req: &Request) {
    println!("Origin: {:?}", req.header::<String>("Origin"));
    println!("Method: {}", req.method());
    println!("Headers: {:?}", req.headers());
}
rust
#[handler]
async fn cors_debug(req: &Request) {
    println!("Origin: {:?}", req.header::<String>("Origin"));
    println!("Method: {}", req.method());
    println!("Headers: {:?}", req.headers());
}

Best Practices

最佳实践

  1. Specify exact origins in production: Avoid
    *
    for security
  2. Limit allowed methods: Only enable methods your API uses
  3. Validate headers: Only allow headers you need
  4. Use HTTPS: Always use HTTPS in production
  5. Set appropriate max_age: Balance security and performance
  6. Apply security headers: Add CSP, HSTS, X-Frame-Options
  7. Test preflight requests: Verify OPTIONS requests work correctly
  1. 生产环境指定精确源:为了安全避免使用
    *
  2. 限制允许的方法:仅启用API实际使用的方法
  3. 验证请求头:仅允许需要的头
  4. 使用HTTPS:生产环境始终使用HTTPS
  5. 设置合适的max_age:平衡安全性和性能
  6. 添加安全头:配置CSP、HSTS、X-Frame-Options等
  7. 测试预检请求:验证OPTIONS请求是否正常工作