【テクニカル・上級編】 APIゲートウェイにおけるCORS(Cross-Origin Resource Sharing)制御 – Web APIアーキテクチャ・データ連携実践ガイド

CORSという名の「見えない壁」を、APIゲートウェイの深層で制御する

Web APIの設計において、RESTの原則を突き詰め、美しいリソース指向のURLを設計できたとしても、ブラウザという「制約だらけのクライアント」が存在する限り、我々はCORS(Cross-Origin Resource Sharing)という古くて新しい難題と向き合い続けなければなりません。

インフラアーキテクトの視点から言えば、CORSは単なるブラウザのセキュリティ機能ではありません。それは、ネットワークの境界(Boundary)において、信頼関係を動的に解決するためのプロトコルそのものです。今日は、APIゲートウェイ層でこのCORSをいかに「エレガントかつ高パフォーマンスに」制御するか、その内部挙動まで掘り下げて解説します。

—

1. プリフライト(OPTIONS)の正体とRTTのコスト

CORSのプリフライトリクエストは、ブラウザが「このリクエストを投げても安全か?」をサーバーに確認するプロセスです。HTTPメソッドに OPTIONS が使われるこの挙動、実はネットワークパフォーマンスの観点からは「無駄な1往復」以外の何物でもありません。

内部挙動の最適化

TCPハンドシェイクが完了し、TLS 1.3の0-RTT(Zero Round-Trip Time)が効く環境であっても、プリフライトが挟まることで、本来のリクエストまでのRTT(Round-Trip Time)が確実に倍増します。

この遅延を最小化するために、APIゲートウェイで取るべき戦略は「レスポンスの即時キャッシュ」です。

# NginxをAPIゲートウェイとして利用する場合の最適化設定
location /api/ {
    # プリフライトの回答をブラウザとゲートウェイでキャッシュさせる
    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' '$http_origin' always;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
        add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always;
        add_header 'Access-Control-Max-Age' 86400; # 24時間は再確認を不要にする
        add_header 'Content-Type' 'text/plain; charset=utf-8';
        add_header 'Content-Length' 0;
        return 204; # 204 No Contentで最速で返す
    }
}

ここで重要なのは Access-Control-Max-Age です。これを適切に設定しないと、クライアントは全てのAPIリクエストのたびに OPTIONS パケットを送り、無駄なRTTを消費し続けます。

—

2. Access-Control-Allow-Originの動的生成:柔軟性とリスクの均衡

最もやってはいけないのが、何でもかんでも Access-Control-Allow-Origin: * とすることです。これは「誰でもどうぞ」という招待状を全人類に配布するようなもの。特に認証情報(CookieやAuthorizationヘッダー)を伴うリクエストにおいて、これは致命的なセキュリティホールになり得ます。

理想的な実装は、ホワイトリストによる動的評価です。

Luaスクリプト(OpenResty)による検証例

Nginxの内部でリクエストヘッダーの Origin をパースし、許可されたドメインのみを動的に Access-Control-Allow-Origin にセットします。

-- OpenResty (Nginx + Lua) での動的制御ロジック
local origin = ngx.var.http_origin
local allowed_origins = {
    ["https://app.example.com"] = true,
    ["https://admin.example.com"] = true
}

if origin and allowed_origins[origin] then
    ngx.header["Access-Control-Allow-Origin"] = origin
    ngx.header["Access-Control-Allow-Credentials"] = "true"
end

この実装のメリットは、バックエンドのアプリケーションロジックに一切触れることなく、ゲートウェイ層で検証を完結できる点です。これにより、アプリケーションサーバーのCPU負荷を軽減し、トランスポート層に近い場所で不正なOriginを弾くことができます。

—

3. パフォーマンスの極致:TLSとバッファチューニング

APIゲートウェイでのCORS制御において、盲点となりがちなのが「TCPウィンドウサイズ」と「ヘッダー圧縮」です。

TCPバッファの最適化

CORSヘッダーを付与することでレスポンスサイズはわずかに増えます。高頻度なAPI呼び出しにおいて、デフォルトの tcp_rmem / tcp_wmem 設定では、パケットの断片化がボトルネックになることがあります。

# Linuxカーネルのネットワークスタック調整例
# 高負荷なAPIゲートウェイでの推奨設定
sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
sysctl -w net.ipv4.tcp_wmem="4096 65536 16777216"

HPACK(HTTP/2)によるヘッダー最適化

HTTP/2およびHTTP/3を使用している場合、Access-Control-Allow-Origin のような繰り返し送られるヘッダーは、HPACK圧縮によってインデックス化されます。これにより、ワイヤ上の転送効率は劇的に向上します。

もし、インフラレベルでCORS制御を一元管理しているなら、クライアント側に Vary: Origin ヘッダーを必ず返すようにしてください。これを忘れると、プロキシサーバーやブラウザのキャッシュが「Origin Aへの許可応答」を「Origin B」に誤って流用し、セキュリティ事故を引き起こすリスクがあります。

—

結論:プロトコルの深淵を覗く

CORSはブラウザのサンドボックスを守るための「足かせ」のように見えますが、その制御をAPIゲートウェイに集約することで、インフラアーキテクトは「どこから誰がアクセスしてきているか」をリアルタイムに制御する強力な武器を手に入れることができます。

パケットのヘッダー一つひとつに意味があり、ハンドシェイクのたびに最適化の余地がある。このプロトコルの深淵を楽しめるようになれば、あなたのAPIはより堅牢で、より速く、そして何より「美しい」ものになるはずです。

次回は、これらのヘッダー制御をHTTP/3 (QUIC) の環境下でいかに低レイテンシに処理するか、ストリームレベルでのハンドリングについて深掘りしていきましょう。

コメント

タイトルとURLをコピーしました