Cosnomi

プログラミングをする医学生。物を作ること、自動化すること、今まで知らなかったことを知ることが好き。TypeScript書いたり、Pythonで機械学習したりなど。

Twitter / GitHub / Keybase
TOP >

同一生成元ポリシー(SOP)を回避する仕組み CORS(オリジン間リソース共有)

以前、 SOP(同一生成元ポリシー)について説明しました。今回は、それを例外的に回避するCORSという仕組みを見ていきます。

軽く復習します。SOPによる制限は厳しいもので、api.example.comとexample.comさえ異なるものとして制限を受けます。これはアクセス元のスクリプトが悪意を持ってクライアントにアクセス先へアクセスさせるのを防ぐためです。つまり、ブラウザとしてはアクセス先がアクセス元を信頼しているということさえ分かれば制限する必要はないわけです。その信頼関係を伝える手段がオリジン管理リソース共有(Cross-Origin Resource Sharing: CORS)です。

概要

いくらSOPによる制限が不便だからといって、いかなる場合でもクロスオリジンのアクセスを認めるなんてことはできません。アクセス元オリジンとアクセス先オリジンに信頼関係があるということが前提です。そして、その信頼関係はアクセス先から確認する必要があります。

信頼関係を確認する仕組み

悪意のないWebサービスがSOPによって不利益を被る最たる例はXMLHttpRequestだと思われますから、これを例にとって説明したいと思います。

単純リクエスト

簡単なリクエストの場合(メソッドがGET/HEAD/POSTのいずれか, 限定されたヘッダー、限定されたContent-Type)は、単純リクエストが行われます。

ブラウザはHTTPリクエストヘッダにアクセス元Originの情報をOriginヘッダーに含めます。オリジンは正規化されて送られます。(例: Origin: http://example.com)

アクセス先サーバーから次のような応答を受け取ることで、アクセス先オリジンとhttp://example.comオリジンは信頼関係にあるとブラウザは判断します。

Access-Control-Allow-Origin: http://example.com」

preflight リクエスト

上記に当てはまらないリクエスト(PUTメソッドの場合やContent-Typeがapplication/jsonの場合など)は実際のリクエストを行う前にpreflightリクエストを行います。これは、単純なリクエスト以外のリクエストをいきなり送りつけるのはユーザーデータに影響を及ぼす可能性があるためです。

preflightリクエストはOPTIONSメソッドを利用して主に以下のヘッダーを送信します。

  • Origin
  • Access-Control-Request-Method (本番の要求で使われるメソッド)
  • Access-Control-Request-Headers (本番の要求で含まれるカスタムヘッダー)

これに基づいて、サーバーはこの要求を受け入れるかどうかを応答します。これには以下が含まれます。

  • Access-Control-Allow-Origin
  • Access-Control-Allow-Methods (許可されるメソッド)
  • Access-Control-Allow-Headers (許可されるヘッダー)
  • Access-Control-Max-Age (preflight要求をキャッシュ可能な秒数)

ブラウザは自分の要求が受け入れられている(つまり、Allow-MethodsやAllow-HeadersにRequest-MethodsやRequest-Headersが含まれる)ことを確認したら単純リクエストと同じように実際のリクエストを行います。

資格情報を含む要求

通常、クロスオリジンのアクセスではブラウザは資格情報(credentials)を送信しません。しかし、明示的な指定があった場合、ブラウザは資格情報を利用したクロスオリジンのアクセスを試みます。

サーバーの単純リクエストに対する応答、またはpreflight要求に対する応答に「Access-Control-Allow-Credentials: true」が含まれていればブラウザは資格情報を含むリクエストによって得られたそのデータの利用を認めます。一方、trueでなければ、その応答を破棄し、Webサイトでのそのデータの利用を認めません。

nginx で Access-Control-Allow-Origin の設定

規定されているヘッダーを含めるようにすれば良いわけです。

単純要求だけしか来ないなら一例として..。

    location / {
       add_header Access-Control-Allow-Origin '*' always;
       add_header Access-Control-Allow-Methods "POST, GET, OPTIONS";
       add_header Access-Control-Allow-Headers "Origin, Content-Type, Authorization";
       add_header Access-Control-Allow-Credentials true;
    }

もし、preflightリクエストも考慮するのであれば、次のようになります。

    location / {
        if ($request_method = 'OPTIONS') {
            add_header Access-Control-Allow-Origin '*' always;
            add_header Access-Control-Allow-Methods "POST, GET, OPTIONS";
            add_header Access-Control-Allow-Headers "Origin, Content-Type, Authorization";
            add_header Access-Control-Allow-Credentials true;
            return 204;
         }
       add_header Access-Control-Allow-Origin '*' always;
       add_header Access-Control-Allow-Methods "POST, GET, OPTIONS";
       add_header Access-Control-Allow-Headers "Origin, Content-Type, Authorization";
       add_header Access-Control-Allow-Credentials true;
    }

まとめ

  • SOPを信頼できるオリジン間に限定して解除するためにCORSという方法がある
  • CORSでは、アクセス先サーバーからAccess-Control-Allow-Originヘッダを受け取る必要がある
  • 複雑なリクエストではpreflightリクエストを行う必要がある
  • サーバー側の設定はnginxのconfigだけでよい

最後まで読んでいただきありがとうございます。コメントフォームは設置していません。訂正・意見・感想などはTwitter(@cosnomi)などへお願いします。
記事一覧へ