クロスオリジン制御

CORS(Cross-Origin Resource Sharing、クロスオリジンリソース共有)は、ブラウザがクロスオリジンのサーバーにリクエストを送信することを許可するメカニズムで、ブラウザの同一オリジンポリシーの制限を克服します。

同一オリジンポリシーとは?

同一オリジンポリシーはブラウザのセキュリティ機能で、あるオリジンから読み込まれたドキュメントやスクリプトが別のオリジンのリソースとどのように相互作用できるかを制限します。「同一オリジン」とは、プロトコル、ドメイン名、ポート番号が同じであることを指します。

CORSが必要な理由

フロントエンドアプリケーションが異なるオリジンのAPIにアクセスする必要がある場合、CORSのサポートが必要です。例:

  • フロントエンドアプリケーションが https://frontend.com にデプロイされている
  • APIサービスが https://api.backend.com にデプロイされている

CORSがない場合、ブラウザはフロントエンドアプリケーションがAPIサービスにアクセスするのをブロックします。

CORSの動作原理

CORSは一連のHTTPヘッダーを通じてクロスオリジンアクセス制御を実現します:

  • シンプルリクエスト:直接送信され、サーバーはレスポンスヘッダーで許可するかどうかを制御
  • プリフライトリクエスト:ブラウザがまずOPTIONSリクエストを送信してクロスオリジン許可を確認し、許可を得てから実際のリクエストを送信

ブラウザが Method::OPTIONS のプリフライトリクエストを送信するため、この種のリクエストを処理する必要があり、CORSミドルウェアを Service に追加する必要があります。

SalvoでのCORSの使用

Salvoには組み込みのCORSミドルウェアが提供されており、簡単に設定して使用できます。以下はサンプルコードです:

WARNING

注意.hoop(cors)ミドルウェアは Service に対して作用します。.hoop(cors)ミドルウェアは Router ではなく Service に対して作用します。 これによりOPTIONSプリフライトが自動的に処理されます。

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

// CORS保護付きでバックエンドルーターを設定
let router = Router::with_path("hello").post(hello);
let service = Service::new(router).hoop(cors);

サンプルコード

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

主な設定オプション

CORSミドルウェアにはさまざまな設定オプションがあります:

  • 許可されるオリジン:どのドメインがリソースにアクセスできるかを制御
  • 許可されるメソッド:許可されるHTTPメソッド(GET、POSTなど)を指定
  • 許可されるヘッダー:許可されるリクエストヘッダーを指定
  • 公開されるヘッダー:クライアントがアクセスできるレスポンスヘッダーを指定
  • 資格情報の許可:リクエストにクッキーなどの資格情報を含めることを許可するかどうか
  • プリフライトリクエストのキャッシュ時間:プリフライトリクエスト結果のキャッシュ時間

CORSを適切に設定することで、セキュリティを確保しつつクロスオリジンアクセスの要件を満たすことができます。