Controllo Cross-Origin

CORS (Cross-Origin Resource Sharing) è un meccanismo che consente ai browser di effettuare richieste a server cross-origin, superando così le restrizioni imposte dalla politica della stessa origine (same-origin policy) del browser.

Cos'è la Politica della Stessa Origine?

La politica della stessa origine è una funzionalità di sicurezza dei browser che limita come documenti o script caricati da un'origine possano interagire con risorse provenienti da un'altra origine. "Stessa origine" si riferisce all'avere lo stesso protocollo, dominio e numero di porta.

Perché è Necessario il CORS?

Il supporto CORS è necessario quando un'applicazione frontend deve accedere ad API da un'origine diversa. Ad esempio:

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

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

Come Funziona il CORS

Il CORS implementa il controllo di accesso cross-origin attraverso una serie di intestazioni HTTP:

  • Richieste Semplici: Inviate direttamente, con il server che controlla se l'accesso è consentito tramite intestazioni di risposta.
  • Richieste Preflight: Il browser invia prima una richiesta OPTIONS per chiedere al server se l'accesso cross-origin è consentito, e invia la richiesta effettiva solo dopo aver ricevuto il permesso.

Poiché il browser invia richieste preflight con Method::OPTIONS, è necessario gestire tali richieste. Il middleware CORS deve essere aggiunto al Service.

Utilizzo del CORS in Salvo

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

Warning

Nota: Il middleware .hoop(cors) viene applicato al Service, non al Router. Ciò garantisce la gestione automatica delle richieste 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();

// 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
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>
"#;

Principali Opzioni di Configurazione

Il middleware CORS offre varie opzioni di configurazione:

  • Origini Consentite: Controlla quali domini possono accedere alle risorse.
  • Metodi Consentiti: Specifica i metodi HTTP consentiti (GET, POST, ecc.).
  • Intestazioni Consentite: Specifica le intestazioni di richiesta consentite.
  • Intestazioni Esposte: Specifica quali intestazioni di risposta possono essere accessibili dal client.
  • Consenti Credenziali: Determina se le richieste possono includere credenziali come i cookie.
  • Tempo di Cache Richieste Preflight: La durata della cache per i risultati delle richieste preflight.

Configurando opportunamente il CORS, è possibile garantire sia la sicurezza che la capacità di soddisfare i requisiti di accesso cross-origin.