🦀 Axum 零 CORS = 浏览器秒拒:Tower-HTTP 默认行为一次看懂

Photos provided by Unsplash OR Pexels

Axum 与 Tower-HTTP 中的 CORS 默认配置实战指南

引言

本指南深入剖析 Axum(Rust 高性能 Web 框架)和 Tower-HTTP(Tower 生态中的 HTTP 服务层)组合中 CORS(Cross-Origin Resource Sharing,跨域资源共享)的默认配置行为。当未显式设置 CORS 时,系统默认不启用任何 CORS 头,这会导致浏览器端跨域请求被阻塞。本指南将从原理分析入手,逐步讲解默认行为、潜在问题及解决方案,并提供一个完整的实战示例项目,包括代码实现、配置文件及测试指南。目标是帮助开发者编写高可读、高可维护、可扩展的工业级代码。

CORS 原理回顾

CORS 是浏览器安全机制,用于控制从不同源(域、协议、端口)发起的请求是否允许访问服务器资源。浏览器在跨域请求时,会根据请求类型执行:

  • 简单请求:如 GET/POST/HEAD,使用特定头,直接发送请求,但服务器需返回 Access-Control-Allow-Origin 等头。
  • 预检请求:复杂请求(如带自定义头或 PUT/DELETE 方法)先发送 OPTIONS 请求,询问服务器是否允许。

Tower-HTTP 提供了 CorsLayer 来处理这些头,支持配置允许的源、方法、头等。Axum 基于 Tower 服务栈构建,可无缝集成 Tower-HTTP 的层。

默认 CORS 配置深入分析

在 Axum 与 Tower-HTTP 组合中,如果未显式添加 CorsLayer 或任何 CORS 处理:

  • 默认行为:Axum 的路由和服务栈不自动注入任何 CORS 相关的响应头(如 Access-Control-Allow-OriginAccess-Control-Allow-Methods 等)。这意味着服务器不会主动允许跨域请求。
    • 对于简单跨域请求:浏览器发送请求,服务器正常响应,但响应中缺少 CORS 头,导致浏览器拒绝解析响应(控制台报错如 “No ‘Access-Control-Allow-Origin’ header is present on the requested resource”)。
    • 对于预检请求:浏览器发送 OPTIONS 请求,Axum 默认不处理 OPTIONS 方法(除非显式路由),通常返回 404 或 405,导致预检失败,实际请求不执行。
  • 潜在问题
    • 安全性:默认不启用 CORS 是安全的,因为它防止了未经授权的跨域访问。但在现代 Web 应用中(如前后端分离),这会阻塞合法前端请求。
    • 性能影响:无 CORS 层时,服务栈更轻量,但需手动处理 OPTIONS 请求以支持复杂跨域。
    • 兼容性:在同源场景下无影响;跨域时需浏览器支持(所有现代浏览器均支持 CORS,但旧版可能有差异)。
  • 为什么默认不启用:Rust 生态强调显式性和安全性。Tower-HTTP 的 CorsLayer 是可选层,用户需根据需求添加,以避免意外暴露资源。相比一些框架(如 Express.js 默认无 CORS),这更符合 Rust 的零开销抽象原则。
  • 源码剖析
    • Axum 核心基于 Hyper 和 Tower,不内置 CORS。
    • Tower-HTTP 的 CorsLayertower_http::cors 模块中实现,默认配置为:
      • allow_origin: AllowOrigin::any()(允许所有源,但需显式设置)。
      • 但未添加层时,等价于 AllowOrigin::none(),即不允许任何跨域。
    • 在服务栈中,未添加层时,响应头为空,浏览器强制应用同源策略。

实战指南:构建 Axum 服务展示默认与自定义 CORS

以下是一个完整示例项目,演示默认 CORS 行为及如何添加自定义配置。项目结构:

  • Cargo.toml:依赖配置文件。
  • src/main.rs:主程序,包含路由、服务栈构建。
  • README.md:测试指南(附属文件)。

步骤 1: 项目初始化

使用 cargo new axum-cors-demo 创建项目。

步骤 2: 配置 Cargo.toml

添加必要依赖:Axum、Tower-HTTP、Tokio(异步运行时)。

[package]
name = "axum-cors-demo"
version = "0.1.0"
edition = "2021"

[dependencies]
axum = "0.7.5"
tower-http = { version = "0.5.2", features = ["cors"] }
tokio = { version = "1.39.3", features = ["full"] }

步骤 3: 实现 src/main.rs

代码分为两部分:默认无 CORS 和添加自定义 CORS。使用条件编译或注释切换。

use axum::{
    http::StatusCode,
    response::IntoResponse,
    routing::{get, post},
    Router,
};
use tower_http::cors::{Any, CorsLayer};

#[tokio::main]
async fn main() {
    // 构建默认无 CORS 的路由
    let app_default = Router::new()
        .route("/", get(root))
        .route("/api/data", post(post_data));

    // 构建添加自定义 CORS 的路由
    let cors_layer = CorsLayer::new()
        .allow_origin(Any)  // 允许所有源(生产环境应限制)
        .allow_methods([axum::http::Method::GET, axum::http::Method::POST, axum::http::Method::OPTIONS])
        .allow_headers(Any);  // 允许所有头

    let app_with_cors = Router::new()
        .route("/", get(root))
        .route("/api/data", post(post_data))
        .layer(cors_layer);

    // 选择运行模式:默认或带 CORS(此处运行默认模式,注释切换)
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app_default).await.unwrap();  // 切换为 app_with_cors 测试自定义
}

// 根路由:简单 GET
async fn root() -> &'static str {
    "Hello, Axum!"
}

// POST 路由:模拟数据处理
async fn post_data() -> impl IntoResponse {
    (StatusCode::OK, "Data received")
}
  • 代码说明
    • 高可读性:使用清晰的函数命名、模块导入。路由分离,便于维护。
    • 高可维护性:CORS 层独立添加,便于配置调整(如从 Any 改为特定源 vec!["http://example.com".parse().unwrap()])。
    • 可扩展性:可添加更多层(如限流、日志)。支持 OPTIONS 处理(CorsLayer 自动处理预检)。
    • 默认模式:运行时无 CORS 层,跨域请求失败。
    • 自定义模式:添加层后,允许指定跨域。

步骤 4: 测试指南 (README.md)

# Axum CORS Demo 测试指南

## 运行项目
1. `cargo run`:启动服务器于 http://localhost:3000。

## 测试默认 CORS(无设置)
- 同源测试:浏览器直接访问 http://localhost:3000/,成功。
- 跨域测试:
  - 使用另一个域(如本地 HTML 文件)发起 fetch('http://localhost:3000/api/data', {method: 'POST'})。
  - 预期:浏览器控制台报 CORS 错误,无响应头。
  - 预检:复杂请求发送 OPTIONS,服务器返回 405(方法不允许)。

## 测试自定义 CORS
- 注释 main 中 app_default,启用 app_with_cors。
- 重启服务器。
- 跨域测试:成功,响应包含 CORS 头如 Access-Control-Allow-Origin: *。

## 工具推荐
- 使用 curl 测试:`curl -X OPTIONS http://localhost:3000/api/data -H "Origin: http://example.com" -v` 查看头。
- 浏览器 DevTools 检查网络请求。

高级配置与最佳实践

  • 限制源:生产中避免 Any,使用 AllowOrigin::list(vec![...]) 指定白名单。
  • 暴露头:使用 .expose_headers([...]) 指定客户端可访问的自定义响应头。
  • 凭证支持:如果需 cookie,使用 .allow_credentials(true),但源不能为 *
  • 错误处理:集成 Axum 的错误处理器,捕获 CORS 相关错误。
  • 性能优化:CORS 层在栈顶添加,避免不必要计算。
  • 安全考虑:仅允许必要方法/头,防止 CSRF 等攻击。
  • 跨平台兼容:代码兼容 Windows/Linux/macOS,无需额外调整。

参考资料

  1. 官方文档
  1. 源码仓库
  1. 社区资源
  1. 版本注意:基于 Axum 0.7.5 和 Tower-HTTP 0.5.2 测试。更新依赖时检查变更日志。

本指南提供完整、可直接运行的示例,确保代码工业级质量。如需扩展,欢迎基于此基础迭代。

版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)