CORS в Axum
Cross-Origin Resource Sharing (CORS) — механизм, который позволяет веб-страницам запрашивать данные с других доменов. В этом разделе рассмотрим, как настроить и использовать CORS в приложениях на Axum.
Содержание
- Что такое CORS
- Настройка CORS с помощью tower-http
- Простая настройка CORS
- Расширенная настройка CORS
- CORS для определенных маршрутов
- Обработка предварительных запросов (preflight)
- Проблемы и их решения
- Безопасность и CORS
Что такое CORS
CORS — это механизм безопасности, который позволяет веб-странице из одного домена (источника) запрашивать и получать данные с сервера другого домена. Без CORS политика безопасности браузера Same-Origin Policy препятствует таким запросам.
Основные компоненты CORS:
- Simple requests — простые запросы, которые не требуют предварительной проверки
- Preflight requests — предварительные запросы OPTIONS, которые проверяют, разрешен ли настоящий запрос
- Заголовки CORS — специальные HTTP заголовки для управления доступом
Настройка CORS с помощью tower-http
Библиотека tower-http
предоставляет удобный слой для настройки CORS в приложениях Axum.
Установка зависимостей
[dependencies]
axum = "0.7.2"
tower-http = { version = "0.5.0", features = ["cors"] }
Простая настройка CORS
Простейший способ добавить поддержку CORS в ваше приложение:
use axum::{
routing::{get, post},
Router,
};
use tower_http::cors::{CorsLayer, Any};
async fn hello() -> &'static str {
"Hello, World!"
}
async fn api_endpoint() -> &'static str {
"{\"message\": \"Это API ответ\"}"
}
#[tokio::main]
async fn main() {
// Создание слоя CORS с базовыми настройками
let cors = CorsLayer::new()
// Разрешить все источники
.allow_origin(Any)
// Разрешить все стандартные HTTP-методы
.allow_methods(Any)
// Разрешить все стандартные заголовки
.allow_headers(Any);
// Создание приложения с CORS
let app = Router::new()
.route("/", get(hello))
.route("/api", post(api_endpoint))
.layer(cors);
// Запуск сервера
let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 3000));
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
Расширенная настройка CORS
Для более детальной настройки CORS:
use axum::{
http::{HeaderName, HeaderValue, Method},
routing::{get, post},
Router,
};
use std::convert::TryFrom;
use tower_http::cors::{AllowOrigin, CorsLayer};
async fn main() {
// Создание более детальной конфигурации CORS
let cors = CorsLayer::new()
// Разрешить только определенные источники
.allow_origin([
"https://myapp.com".parse::<HeaderValue>().unwrap(),
"https://api.myapp.com".parse::<HeaderValue>().unwrap(),
])
// Разрешить только определенные методы
.allow_methods([Method::GET, Method::POST, Method::PUT])
// Разрешить только определенные заголовки
.allow_headers([
HeaderName::from_static("authorization"),
HeaderName::from_static("content-type"),
])
// Разрешить кредиталы (cookies, auth headers)
.allow_credentials(true)
// Установить максимальный срок кеширования preflight запросов
.max_age(std::time::Duration::from_secs(3600));
// Создание приложения с настроенным CORS
let app = Router::new()
.route("/", get(hello))
.route("/api", post(api_endpoint))
.layer(cors);
// Запуск сервера
let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 3000));
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
CORS для определенных маршрутов
Можно настроить разные политики CORS для разных маршрутов:
use axum::{
http::{HeaderValue, Method},
routing::{get, post},
Router,
};
use tower_http::cors::{CorsLayer, Any};
#[tokio::main]
async fn main() {
// Создание CORS для публичного API
let public_cors = CorsLayer::new()
.allow_origin(Any)
.allow_methods([Method::GET])
.allow_headers(Any);
// Создание CORS для административного API
let admin_cors = CorsLayer::new()
.allow_origin("https://admin.myapp.com".parse::<HeaderValue>().unwrap())
.allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE])
.allow_headers(Any)
.allow_credentials(true);
// Публичные маршруты с одной политикой CORS
let public_routes = Router::new()
.route("/api/public", get(public_api))
.layer(public_cors);
// Административные маршруты с другой политикой CORS
let admin_routes = Router::new()
.route("/api/admin", get(admin_api))
.layer(admin_cors);
// Объединение маршрутов
let app = Router::new()
.merge(public_routes)
.merge(admin_routes);
// Запуск сервера
let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 3000));
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
async fn public_api() -> &'static str {
"Это публичный API"
}
async fn admin_api() -> &'static str {
"Это административный API"
}
Обработка предварительных запросов (preflight)
Preflight запросы (OPTIONS) обрабатываются автоматически слоем CORS. Однако вы можете добавить собственный обработчик для OPTIONS запросов:
use axum::{
http::{Method, StatusCode},
routing::{get, on, MethodRouter},
Router,
};
use tower_http::cors::CorsLayer;
// Обработчик для OPTIONS запросов
async fn options_handler() -> StatusCode {
StatusCode::NO_CONTENT
}
#[tokio::main]
async fn main() {
// Настройка CORS
let cors = CorsLayer::new()
.allow_origin("https://myapp.com".parse().unwrap())
.allow_methods([Method::GET, Method::POST, Method::OPTIONS])
.allow_headers(Any);
// Маршруты с явной обработкой OPTIONS
let app = Router::new()
.route("/api",
get(api_endpoint)
.options(options_handler)
)
.layer(cors);
// Запуск сервера
// ...
}
Проблемы и их решения
Распространенные проблемы CORS
1. "No 'Access-Control-Allow-Origin' header is present"
Решение:
let cors = CorsLayer::new()
.allow_origin("https://yourfrontend.com".parse::<HeaderValue>().unwrap())
// или для разрешения всех источников:
// .allow_origin(Any)
.allow_methods(Any);
2. "Method not allowed"
Решение:
let cors = CorsLayer::new()
.allow_origin(Any)
.allow_methods([
Method::GET,
Method::POST,
Method::PUT,
Method::DELETE,
Method::OPTIONS,
]);
3. "Request header field X-Custom-Header is not allowed"
Решение:
let cors = CorsLayer::new()
.allow_origin(Any)
.allow_headers([
HeaderName::from_static("x-custom-header"),
HeaderName::from_static("content-type"),
// ...другие необходимые заголовки
]);
4. "Credentials is not supported if the CORS header 'Access-Control-Allow-Origin' is '*'"
Решение:
let cors = CorsLayer::new()
// Необходимо указать конкретные источники при использовании credentials
.allow_origin("https://yourfrontend.com".parse::<HeaderValue>().unwrap())
.allow_credentials(true);
Динамическое управление CORS
Для более сложных сценариев вы можете создать собственную функцию для определения CORS политики:
use std::task::{Context, Poll};
use tower::{Layer, Service};
use axum::http::{Request, Response, HeaderValue};
use futures::future::BoxFuture;
// Структура для динамического CORS
struct DynamicCorsLayer;
impl<S> Layer<S> for DynamicCorsLayer {
type Service = DynamicCorsService<S>;
fn layer(&self, service: S) -> Self::Service {
DynamicCorsService { inner: service }
}
}
struct DynamicCorsService<S> {
inner: S,
}
impl<S, B, ReqBody> Service<Request<ReqBody>> for DynamicCorsService<S>
where
S: Service<Request<ReqBody>, Response = Response<B>> + Clone + Send + 'static,
S::Future: Send + 'static,
ReqBody: Send + 'static,
B: Send + 'static,
{
type Response = S::Response;
type Error = S::Error;
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, mut req: Request<ReqBody>) -> Self::Future {
let origin = req
.headers()
.get("origin")
.and_then(|h| h.to_str().ok())
.map(|s| s.to_string());
let service = self.inner.clone();
let mut inner = std::mem::replace(&mut self.inner, service);
Box::pin(async move {
let mut response = inner.call(req).await?;
// Динамически определяем CORS заголовки на основе origin
if let Some(origin) = origin {
// Проверка соответствия origin списку разрешенных
let allowed = is_allowed_origin(&origin);
if allowed {
response.headers_mut().insert(
"access-control-allow-origin",
HeaderValue::from_str(&origin).unwrap(),
);
response.headers_mut().insert(
"access-control-allow-methods",
HeaderValue::from_static("GET, POST, PUT, DELETE, OPTIONS"),
);
response.headers_mut().insert(
"access-control-allow-headers",
HeaderValue::from_static("content-type, authorization"),
);
response.headers_mut().insert(
"access-control-allow-credentials",
HeaderValue::from_static("true"),
);
}
}
Ok(response)
})
}
}
// Проверка, входит ли origin в список разрешенных
fn is_allowed_origin(origin: &str) -> bool {
let allowed_origins = [
"https://myapp.com",
"https://staging.myapp.com",
"http://localhost:3000",
];
allowed_origins.contains(&origin)
}
Безопасность и CORS
Рекомендации по безопасности
Не используйте
allow_origin(Any)
для production- Всегда указывайте конкретные домены, с которых разрешены запросы
rustlet cors = CorsLayer::new() .allow_origin([ "https://myapp.com".parse::<HeaderValue>().unwrap(), "https://api.myapp.com".parse::<HeaderValue>().unwrap(), ]);
Ограничивайте методы и заголовки
- Разрешайте только те методы и заголовки, которые действительно используются
rustlet cors = CorsLayer::new() .allow_methods([Method::GET, Method::POST]) .allow_headers([ HeaderName::from_static("content-type"), HeaderName::from_static("authorization"), ]);
Будьте осторожны с
allow_credentials(true)
- Помните, что это позволяет передавать файлы cookie и заголовки аутентификации
- Обязательно указывайте конкретные домены при использовании credentials
Используйте подход "белого списка"
- Явно указывайте разрешенные источники, методы и заголовки
- Избегайте использования
Any
в production-окружении
CORS не заменяет другие меры безопасности
- CORS — это механизм безопасности браузера, он не защищает от запросов, выполняемых вне браузера
- Всегда используйте дополнительные меры безопасности: аутентификацию, авторизацию, валидацию входных данных
Примеры для разных окружений
Настройка CORS для разработки
// Для локальной разработки
let dev_cors = CorsLayer::new()
.allow_origin([
"http://localhost:3000".parse::<HeaderValue>().unwrap(),
"http://localhost:8080".parse::<HeaderValue>().unwrap(),
])
.allow_methods(Any)
.allow_headers(Any)
.allow_credentials(true);
Настройка CORS для production
// Для production
let prod_cors = CorsLayer::new()
.allow_origin([
"https://myapp.com".parse::<HeaderValue>().unwrap(),
"https://www.myapp.com".parse::<HeaderValue>().unwrap(),
])
.allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE])
.allow_headers([
HeaderName::from_static("content-type"),
HeaderName::from_static("authorization"),
])
.allow_credentials(true)
.max_age(Duration::from_secs(3600));
Правильная настройка CORS критически важна для создания безопасных веб-приложений, особенно тех, которые используют фронтенд, размещенный на отдельном домене от API. Axum с Tower HTTP предоставляет гибкий и удобный способ управления CORS политиками для вашего приложения.