Control de Origen Cruzado

CORS (Intercambio de Recursos de Origen Cruzado) es un mecanismo que permite a los navegadores realizar solicitudes a servidores de origen cruzado, superando así las restricciones impuestas por la política de mismo origen del navegador.

¿Qué es la Política de Mismo Origen?

La política de mismo origen es una característica de seguridad de los navegadores que restringe cómo los documentos o scripts cargados desde un origen pueden interactuar con recursos de otro origen. "Mismo origen" se refiere a tener el mismo protocolo, dominio y número de puerto.

¿Por qué se Necesita CORS?

El soporte de CORS es necesario cuando una aplicación frontend necesita acceder a APIs de un origen diferente. Por ejemplo:

  • La aplicación frontend está desplegada en https://frontend.com
  • El servicio API está desplegado en https://api.backend.com

Sin CORS, el navegador impedirá que la aplicación frontend acceda al servicio API.

Cómo Funciona CORS

CORS implementa el control de acceso de origen cruzado a través de una serie de encabezados HTTP:

  • Solicitudes Simples: Se envían directamente, con el servidor controlando si se permite el acceso mediante encabezados de respuesta.
  • Solicitudes Preflight: El navegador primero envía una solicitud OPTIONS para preguntar al servidor si se permite el acceso de origen cruzado, y solo envía la solicitud real después de recibir permiso.

Dado que el navegador envía solicitudes preflight con Method::OPTIONS, es necesario manejar dichas solicitudes. El middleware CORS debe agregarse al Service.

Uso de CORS en Salvo

Salvo proporciona middleware CORS integrado que puede configurarse y usarse fácilmente. Aquí hay un fragmento de código de ejemplo:

Warning

Nota: El middleware .hoop(cors) se aplica al Service, no al Router. Esto garantiza el manejo automático de las solicitudes preflight OPTIONS.

let cors = Cors::new()
    .allow_origin(["http://127.0.0.1:8698", "http://localhost:8698"])
    .allow_methods(vec![Method::GET, Method::POST, Method::DELETE])
    .allow_headers("authorization")
    .into_handler();

// Configurar el router del backend con protección CORS
let router = Router::with_path("hello").post(hello);
let service = Service::new(router).hoop(cors);

Código de Ejemplo

main.rs
Cargo.toml
use salvo::cors::Cors;
use salvo::http::Method;
use salvo::prelude::*;

#[tokio::main]
async fn main() {
    // Initialize logging system
    tracing_subscriber::fmt().init();
    // Start both backend and frontend servers concurrently
    tokio::join!(backend_server(), frontend_server());
}

async fn backend_server() {
    // Handler that returns a simple message for CORS demonstration
    #[handler]
    async fn hello() -> &'static str {
        "hello, I am content from remote server."
    }

    // Configure CORS middleware with specific settings:
    // - Allow requests from localhost:8698
    // - Allow specific HTTP methods
    // - Allow authorization header
    let cors = Cors::new()
        .allow_origin(["http://127.0.0.1:8698", "http://localhost:8698"])
        .allow_methods(vec![Method::GET, Method::POST, Method::DELETE])
        .allow_headers("authorization")
        .into_handler();

    // Set up backend router with CORS protection
    let router = Router::with_path("hello").post(hello);
    let service = Service::new(router).hoop(cors);

    // Start backend server on port 5600
    let acceptor = TcpListener::new("0.0.0.0:5600").bind().await;
    Server::new(acceptor).serve(service).await;
}

async fn frontend_server() {
    // Handler that serves the HTML page with CORS test
    #[handler]
    async fn index() -> Text<&'static str> {
        Text::Html(HTML_DATA)
    }

    // Set up frontend router to serve the test page
    let router = Router::new().get(index);
    // Start frontend server on port 8698
    let acceptor = TcpListener::new("0.0.0.0:8698").bind().await;
    Server::new(acceptor).serve(router).await;
}

// HTML template with JavaScript code to test CORS
// Contains a button that triggers a POST request to the backend server
const HTML_DATA: &str = r#"
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Salvo Cors</title>
</head>
<body>
<button id="btn">Load Content</button>
<div id="content"></div>
<script>
document.getElementById("btn").addEventListener("click", function() {
    fetch("http://127.0.0.1:5600/hello", {method: "POST", headers: {authorization: "abcdef"}}).then(function(response) {
        return response.text();
    }).then(function(data) {
        document.getElementById("content").innerHTML = data;
    });
});
</script>
</body>
</html>
"#;

Opciones Principales de Configuración

El middleware CORS proporciona varias opciones de configuración:

  • Orígenes Permitidos: Controla qué dominios pueden acceder a los recursos.
  • Métodos Permitidos: Especifica los métodos HTTP permitidos (GET, POST, etc.).
  • Encabezados Permitidos: Especifica los encabezados de solicitud permitidos.
  • Encabezados Expuestos: Especifica qué encabezados de respuesta pueden ser accedidos por el cliente.
  • Permitir Credenciales: Determina si las solicitudes pueden incluir credenciales como cookies.
  • Tiempo de Caché de Solicitud Preflight: La duración del caché para los resultados de las solicitudes preflight.

Al configurar CORS adecuadamente, puedes garantizar tanto la seguridad como la capacidad de cumplir con los requisitos de acceso de origen cruzado.