Cross-Origin-Ressourcenfreigabe (CORS)

CORS (Cross-Origin Resource Sharing) ist ein Mechanismus, der es Browsern ermöglicht, Anfragen an Server unterschiedlicher Herkunft zu senden und somit die Same-Origin-Policy des Browsers zu umgehen.

Was ist die Same-Origin-Policy?

Die Same-Origin-Policy ist eine Sicherheitsfunktion des Browsers, die einschränkt, wie Dokumente oder Skripte, die von einer Quelle geladen wurden, mit Ressourcen einer anderen Quelle interagieren können. "Gleiche Herkunft" bedeutet dabei identisches Protokoll, Domain und Portnummer.

Warum wird CORS benötigt?

Wenn eine Frontend-Anwendung auf APIs unterschiedlicher Herkunft zugreifen muss, ist CORS erforderlich. Beispiel:

  • Frontend-Anwendung bereitgestellt unter https://frontend.com
  • API-Service bereitgestellt unter https://api.backend.com

Ohne CORS würde der Browser den Zugriff der Frontend-Anwendung auf den API-Service blockieren.

Funktionsweise von CORS

CORS steuert den Cross-Origin-Zugriff über eine Reihe von HTTP-Headern:

  • Einfache Anfragen: Werden direkt gesendet, der Server kontrolliert über Antwort-Header, ob sie erlaubt sind
  • Preflight-Anfragen: Der Browser sendet zunächst eine OPTIONS-Anfrage, um die Erlaubnis für Cross-Origin-Zugriff abzufragen, bevor die eigentliche Anfrage gesendet wird

Da der Browser Preflight-Anfragen mit Method::OPTIONS sendet, muss die Behandlung solcher Anfragen implementiert werden. Dazu muss das CORS-Middleware dem Service hinzugefügt werden.

Verwendung von CORS in Salvo

Salvo bietet ein integriertes CORS-Middleware, das einfach konfiguriert und verwendet werden kann. Hier ein Beispielcode:

WARNING

Hinweis. Das .hoop(cors)-Middleware wirkt auf den Service, nicht auf den Router. Dadurch werden OPTIONS-Preflight-Anfragen automatisch behandelt.

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();

// Backend-Router mit CORS-Schutz einrichten
let router = Router::with_path("hello").post(hello);
let service = Service::new(router).hoop(cors);

Beispielcode

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

Hauptkonfigurationsoptionen

Das CORS-Middleware bietet verschiedene Konfigurationsmöglichkeiten:

  • Erlaubte Ursprünge: Steuert, welche Domänen auf Ressourcen zugreifen dürfen
  • Erlaubte Methoden: Legt die erlaubten HTTP-Methoden fest (GET, POST etc.)
  • Erlaubte Header: Spezifiziert die erlaubten Anfrage-Header
  • Verfügbar gemachte Header: Legt fest, welche Antwort-Header für Clients sichtbar sein sollen
  • Erlaubte Credentials: Ob Anfragen Credentials wie Cookies enthalten dürfen
  • Preflight-Cache-Zeit: Wie lange das Ergebnis von Preflight-Anfragen zwischengespeichert wird

Durch angemessene CORS-Konfiguration können sowohl Sicherheit gewährleistet als auch Cross-Origin-Zugriffsanforderungen erfüllt werden.