理解が難しいCORSに関して簡単に説明します。
CORSを簡単に解説
CORS (Cross-Origin Resource Sharing) オリジン間リソース共有はMozillaのホームページによると
追加の HTTP ヘッダーを使用して、あるオリジンで動作しているウェブアプリケーションに、異なるオリジンにある選択されたリソースへのアクセス権を与えるようブラウザーに指示するための仕組み https://developer.mozilla.org/ja/docs/Web/HTTP/CORS
オリジン間リソース共有 (CORS) – HTTP | MDN https://developer.mozilla.org/ja/docs/Web/HTTP/CORS
と説明されています。
これだけでは意味がわからないと思いますので、詳しく見ていきます。
登場用語の整理
オリジン
オリジンはユーザのブラウザが要求するデータを送信してくれる配信元のサーバのことです。
WEBアプリ
WEBアプリはインターネットを見るブラウザ上で利用できるアプリです。WordpressのブログサイトもWEBアプリの一つなので、単純なウェブサイトと捉えても問題ないです。
異なるオリジン
ユーザが使ったWEBアプリがあるオリジンとは別のサーバからデータを取得しなければならない時、別のオリジンは元のオリジンとは異なるオリジンです。
なぜわざわざ、別のオリジンから取得してるのか?
これは様々な理由がありますが、
- 負荷分散
- 大容量な画像や動画のダウンロードで負荷をアプリサーバにかけたくない
- 機能分離
- アプリサーバとコンテンツ配信サーバを機能的に分離したい
- 他アプリとの共用
- アプリAやアプリBで利用しているコンテンツサーバを共用したい
負荷を分散したり、機能的にオリジンを分けたいといった理由で分けることがあります。
しかし、デフォルトでは別オリジンからデータを取得することはセキュリティ上、禁止されています。
CORSは制約を回避するため
CORSがない場合は別オリジンにある画像の取得ができません。
別オリジンから画像を取得する場合は、別オリジンのhttps://static.example.comをCORSで許可する設定が必要です。
同一オリジンとは、同じスキーム、ホスト、ポート全てが一致するものを指します
スキーム:http, htpps
ホスト:example.com
ポート:80, 443, 8080
別オリジンへのリクエストが禁止されている音はCSRF攻撃という脅威があるからです。
CSRF(クロスサイトリクエストフォージェリ)は全く違う悪意のあるサイトURLにアクセスしたときにクロスサイトリクエストにより別のサイトの情報を抜き取られたり、設定変更などが行われてしまうことです。
会員制Aサイト(https://member.example.com)にログインしている状態
↓
迷惑メールのURL(https://csrf.example.com)をクリック
↓
知らないサイトに誘導(https://csrf.example.com)
↓
迷惑メールのURL中に会員制Aサイトhttps://member.example.comに対してリクエストを送信するコードが埋め込まれていて実行してしまう
↓
ユーザはcsrf.example.comにアクセスしただけだが、会員制Aサイトのプロフィール情報が攻撃者の元に送信されたり、会員制Aサイトで不正にカード利用されたりする可能性がある
この一連の攻撃を防止するには異なるオリジンへのリクエストをデフォルトで禁止するのが有効です。
CORSを設定する
MmdnのドキュメントにあるようにCORSではhttpヘッダーを利用します。
HTTPヘッダーを使ってリソース取得を許可するオリジンを指定します。
Access-Control-Allow-Origin:のヘッダーに*を設定 → 全許可
httpヘッダーにCORSに利用する情報を追記します。
例えば、Access-Control-Allow-Origin: *というヘッダーは全オリジンへのリクエストが許可されています。
特定のオリジンのみを許可する
コンテンツサーバ: https://static.example.comに対して、アプリサーバ:https://example.com からリクエストが飛ぶとします。
コンテンツサーバstatic.example.comは信頼できるhttps://example.comのクロスサイトリクエストは許可したいが、それ以外のhttps://akui.example.comなどは禁止したいという場合は以下のようにAccess -Control-Allow-Originヘッダーに記載します。
Access-Control-Allow-Origin: https://example.com
CORSの2つのシナリオ
次に具体的なCORSのシナリオについて説明します。
元オリジン:https://example.com
別オリジン:https://static.example.com とします。
クロスオリジンリクエストには以下のシナリオがあります。
- 単純リクエスト
- プリフライトリクエスト
単純リクエスト
単純リクエストは下記の条件を満たすものです
- メソッド:GET, HEAD, POST
- ヘッダー: Accept, Accept-Language, Content-Language, Content-Type, Range, (Connection User-Agent)
- Content-Typeはapplication/x-www-form-urlencoded, multipart/form-data, text/plainのみ
- ReadableStreamオブジェクト未使用
- XMLHttpRequest.upladプロパティから返されるオブジェクトにイベントリスナーが登録されていない
元のオリジンexample.comから別のオリジンのリソースを参照する場合には、別オリジンhttps://static.examplec.comはリクエストヘッダのAccec-Control-Allow-Originを参照して許可オリジンかどうか判断します。
1. クライアントが元オリジンhttps://example.comにアクセス
2.元オリジンは自身がオリジンであることを示すorignヘッダーを付加してクライアントに返す
Origin: https://example.com
3. クライアントは元オリジンから受け取ったJavaScriptの処理により、別オリジンhttps://static.example.comのリソースを受け取りに別オリジンにリクエストを送信する
4. 別オリジンhttps://static.example.comはoriginヘッダーを見る
5. 別オリジンはexample.comは許可しているのでAccess-Contral -Allow -Originヘッダーを付加してクライアントにレスポンスを返す
Access-Control-Allow-Origin: https://example.com
6. これでクライアントは別オリジンのデータを取得できた
Webブラウザがサーバにデータをくれという要求をリクエスト
サーバがリクエストに応答してデータを返すのがレスポンス
HTTPプロトコルを利用する
プリフライトリクエスト
プリフライトリクエストは別のオリジンにリクエストを送信する前に確認のリクエストを送信する方法です(許可オリジンか確認するやり取りが一往復増える)。
単純リクエストの条件外では「プリフライトリクエスト」で行われます。
プリフライトリクエストでは、はじめにOPTIONメソッドを別オリジンに送信して信頼できるオリジンかを判断します。
1. クライアントは別オリジンにOPTIONSメソッドでリクエストを送信
OPTIONSメソッドには以下3つのヘッダーを含む(値は例)
・Origin:https://example.com
・Access-Control-Request-Method:POST
・Access-Control-Request-Headers: X-PINGOTHER, Content-Type
2.プリフライトリクエストを受け取ったサーバ(https://static.example.com)はレスポンスを返す(許可の内容が書かれれてる)OKの場合は2xxの応答がかえる(200や204)
・Access-Control-Allow-Origin:https://example.com
・Access-Control-Request-Method:POST, GET, OPTIONS
・Access-Control-Request-Headers: X-PINGOTHER, Content-Type
3. ブラウザはプリフライトリクエストの結果、許可されている場合は本来のPOSTを送信してリソースを取りにいく
4.別オリジンはレスポンスでリソースを返却する
Access-Control-Allow-Originにはカンマ区切りなどで複数の値を入れることができません。
複数入れる場合はバックエンド側のWebサーバ設定で正規表現を使って判断し、マッチしたAccess-Control-Allow-Originoriginをヘッダーにセットして返却というように設定します。