「CORSとは何か」について基本的な情報をまとめました。
開発中に「CORSエラー」に出くわしたときに、戸惑わずに調べていけることを目的としています
スライド
説明
前提知識
オリジン
オリジンはそのWEBページの場所を示すものです。
まず、URLをイメージしてください。URLは主に以下の要素から構成されています。
- スキーム(プロトコル)
- ホスト(ドメイン)
- ポート番号
- パス
このURLのスキームからポート番号までがオリジンと呼ばれます。
1つでも違う要素があれば、それは違うオリジンであると言えます。
ちなみに、ポート番号を普段ウェブブラウザを使用していて見かけることはあまりないと思います。
これはウェブブラウザが使うデフォルトのポート番号のときは、ウェブブラウザはポート番号を非表示にするからです。
SOP
SOPは「Same Origin Policy」の略称です。日本語では、同一オリジンポリシーとも呼ばれます。
通常、このSOPにより異なるオリジン同士のやりとりは制限されています。
SOPはユーザーのセキュリティを守ることなどに役立っています。
例えば、他のWEBページのログイン情報を読み取りこのWEBページに送信するスクリプトを含む、悪意のあるWEBページがあったとします。
もし、SOPがなければ、ユーザーが標的となるWEBページを別のタブで開いているときに、このWEBページを閲覧するとスクリプトが実行されてログイン情報が盗まれてしまう可能性があります。
悪意のあるウェブページとターゲットとなるウェブページは異なるオリジンを持つため、SOPが存在することで、これらの間の通信が制限されユーザーの情報が保護されます。
CORS
SOPがあることでユーザーのセキュリティが守られていると先述しましたが、異なるオリジン間で意図的に通信したいときに困ってしまいます。
そこで登場するのが「CORS」です。
CORSとは「Cross-Origin Resource Sharing」の頭文字を取ったものです。
文字通り、異なるオリジン感でのリソースを共有する仕組みのことです。リクエスト元とリクエスト先の双方が設定したルール則った通信であれば、異なるオリジン間であっても通信することが可能になります。
CORSリクエストの流れ
ブラウザは元のページとは別のオリジンへのリクエストがあった際に、リクエスト先のサーバーにリクエスト元のオリジンなどの必要な情報をヘッダーに含めて伝えます。
具体的には以下のような情報があります。
- 通信プロトコル
- Origin
- Access-Control-Request-Method(リクエストメソッド)
- Access-Control-Request-Headers(データの種類など)
リクエスト先のサーバーは要求されたデータと共に、ヘッダーに「Access-Control-Allow-Origin」を始めとしたCORSに関わる情報をつけてレスポンスします。
具体的には以下のような以下のような情報があります。
- Access-Control-Allow-Origin
- Access-Control-Allow-Methods
- Access-Control-Allow-Headers
ブラウザはこれらの通信を許可する先の情報のようなものを読み取ることでリクエスト元にデータを渡してよいかどうかを判断します。
もし「Access-Control-Allow-Origin」にリクエスト元のオリジンが含まれていなかったり、許可するメソッドでなかったりする場合は、ブラウザはCORSエラーを発生させ、リクエスト元へデータを渡しません。
CORSリクエストの種類
CORSには2種類のリクエストが存在します。
ブラウザとリクエスト先とのやりとりが1回だけの場合と、一度リクエストしてもよいかリクエスト先に確認してからデータ取得などの本番のリクエストを行う、2回のやりとりが発生する場合があります。
やりとりが1回だけでいきなり本番のリクエストを行うものを「シンプルリクエスト」、一度確認してから本番リクエストを行うものを「プリフライトリクエスト」と呼びます。
プリフライトリクエストでは、1回目のリクエストメソッドに「OPTION」が使われることが特徴的です。
これらのリクエストは自由に選択できるわけではなく、リクエストの内容によってどちらが使用されるかが変わってきます。
具体的には以下のような特定の条件をすべて満たすものはシンプルリクエストとなり、そうでない場合はプリフライトリクエストになります。
-
メソッドが以下のいずれかであること
- GET
- HEAD
- POST
-
ヘッダーに以下以外が設定されていないこと
- Accept
- Accept-Language
- Content-Language
- Content-Type
-
Content-Typeヘッダーが設定される場合は、以下のいずれかであること
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
詳細な条件についてはこちらのページを参照してください。
おまけ
ブラウザがリクエスト元にデータを渡すかを判断しているのであれば、許可されていないオリジンの場合でもレスポンスと一緒にリクエストしたデータは返ってきているのではと気になって調べてみました。
『体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 脆弱性が生まれる原理と対策の実践』に倣って実際のレスポンスを見てみました。
郵便番号を返すAPIに対してリクエストを行い、郵便番号を画面に表示するスクリプトを使っています。
左側の窓がリクエストで右側の窓がレスポンスです。
許可されているオリジンの場合
オリジンhttp://example.jp
は許可されているので問題なく取得できています。
画面上にも郵便番号が表示されました。
許可されていないオリジンの場合(シンプルリクエスト)
オリジンhttp://example.jp
は許可されてい無いにも関わらずデータ自体は返ってきています。
ただし、画面上には郵便番号は出力されず、開発者ツールのコンソール上にはCORSエラーが出力されていました。
上記の結果からCORSはあくまでブラウザで実施されるセキュリティ対策であり、独自のブラウザやツールを使用すればCORSを無視することができてしまうことがわかりました。
実際の開発ではCORSだけでなくサーバー側でのセキュリティ対策も必ず実施する必要があります。
参考資料
オリジン間リソース共有 (CORS) - HTTP | MDN (mozilla.org)
徳丸 浩 (2018)『体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 脆弱性が生まれる原理と対策の実践』 SBクリエイティブ