Controllo CORS (Cross-Origin Resource Sharing)

Il CORS (Cross-Origin Resource Sharing, condivisione di risorse tra origini diverse) è un meccanismo che consente ai browser di effettuare richieste a server di origini diverse, superando le limitazioni della politica della stessa origine.

Cos'è la politica della stessa origine?

La politica della stessa origine è una funzionalità di sicurezza dei browser che limita come un documento o script caricato da un'origine può interagire con risorse provenienti da un'altra origine. Per "stessa origine" si intende lo stesso protocollo, dominio e numero di porta.

Perché serve il CORS?

Quando un'applicazione frontend deve accedere a API di origini diverse, è necessario il supporto CORS. Ad esempio:

  • L'applicazione frontend è ospitata su https://frontend.com
  • Il servizio API è ospitato su https://api.backend.com

Senza CORS, il browser impedirebbe all'applicazione frontend di accedere al servizio API.

Come funziona il CORS

Il CORS gestisce il controllo degli accessi tra origini diverse attraverso una serie di header HTTP:

  • Richieste semplici: inviate direttamente, il server controlla se consentirle tramite header di risposta
  • Richieste preflight: il browser invia prima una richiesta OPTIONS per chiedere al server se consente l'accesso tra origini diverse, e solo dopo aver ricevuto l'autorizzazione invia la richiesta effettiva

Poiché il browser invia richieste preflight Method::OPTIONS, è necessario gestire questo tipo di richieste aggiungendo il middleware CORS al Service.

Utilizzare il CORS in Salvo

Salvo fornisce un middleware CORS integrato che può essere facilmente configurato e utilizzato. Ecco un esempio di codice:

WARNING

Attenzione. Il middleware .hoop(cors) viene applicato al Service, non al Router. In questo modo le richieste preflight OPTIONS vengono gestite automaticamente.

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

// Configura il router backend con protezione CORS
let router = Router::with_path("hello").post(hello);
let service = Service::new(router).hoop(cors);

Codice di esempio

main.rs
Cargo.toml
cors/src/main.rs
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:5800
    // - Allow specific HTTP methods
    // - Allow authorization header
    let cors = Cors::new()
        .allow_origin(["http://127.0.0.1:5800", "http://localhost:5800"])
        .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 5800
    let acceptor = TcpListener::new("0.0.0.0:5800").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>
"#;

Opzioni principali di configurazione

Il middleware CORS offre diverse opzioni di configurazione:

  • Origini consentite: controlla quali domini possono accedere alle risorse
  • Metodi consentiti: specifica i metodi HTTP permessi (GET, POST, ecc.)
  • Header consentiti: specifica gli header di richiesta permessi
  • Header esposti: specifica quali header di risposta possono essere accessibili dal client
  • Credenziali consentite: indica se le richieste possono includere credenziali come cookie
  • Durata cache richieste preflight: tempo di memorizzazione nella cache dei risultati delle richieste preflight

Configurando opportunamente il CORS, è possibile garantire sia la sicurezza che le necessità di accesso tra origini diverse.