CORSで起こっていること
はじめに
開発中のWebアプリケーションでCORS対応が必要になったものの、CORS?なにそれおいしいの?状態だったので調べてまとめてみます。
CORSとは
CORSとは、異なるオリジンのリソースへのアクセス権を与える仕組み
CORSはCross-Origin Resoure Sharingの略で、日本語だと「オリジン間リソース共有」のこと。
ブラウザの仕組みで、ブラウザからHTTPリクエストでサーバーのリソースにアクセスする際に使用されている。※オリジンとは?
雑な説明だと、WebサイトAからWebサイトBのリソースにアクセスできるようにしましょうという仕組み。これがあるおかげで、閲覧しているサイトから別サイトへのリンクが叩けたり、こんな風に別サイトの中身を表示できたりする↓
起きていること
クライアントとサーバーとのやり取りの中でブラウザは、
- リクエストヘッダ―の
Origin
:リクエストが発生したオリジン - レスポンスヘッダーの
Access-Controll-Allow-Origin
:アクセスを許可するオリジン
を比べて、2つが同一であればレスポンスの中身を取得する。
例)リクエストヘッダ―
Origin: http://localhost:8080
例)レスポンスヘッダー
Access-Controll-Allow-Origin: http://localhost:8080
どちらかが欠けていたり、2つのオリジンが異なる場合、ブラウザはレスポンスを破棄しエラーを返す。
ここで注意したいのは、CORSはブラウザ側の仕様であり、リクエスト時のヘッダー情報とレスポンス時のヘッダー情報を比較している点である。つまりアクセス許可がなかった場合は、リクエストが送られないのではなく、ブラウザがリクエストを送りレスポンスを一度受け取ってから破棄している。
CORSを実装するには
リクエストヘッダーはブラウザが自動でつけてくれる
リクエストでは、ヘッダーにOrigin
という「どこのオリジン(ホスト)でリクエストが発生したか?」という情報が記述される。
これはブラウザからリクエストを送る際に自動で追加されるため、実装は不要。
レスポンスヘッダーはサーバーで設定が必要
レスポンスでは、ヘッダー情報としてアクセスを許可するオリジンは何かを送信する必要がある。
これは自動で追加されないので、サーバー側で実装が必要。
以下のように記述する。
例)http://localhost:8080からのアクセスを許可する
Access-Controll-Allow-Origin: http://localhost:8080
例)すべてのオリジンからのアクセスを許可する
Access-Controll-Allow-Origin: *
プリフライトリクエスト
CORSの一部で、実際のリクエストを送る前にリクエストを送っても問題ないか?を確認するためのリクエストを送信するというもの。
『CORSとは』で前述したとおり、CORSではアクセス許可があるかを確かめるために、ブラウザは一度リクエストを送る必要がある。
そのため、GET、POST、HEADでリソースを閲覧するだけなら問題ないが、DELETEメゾットなどのリクエストでリソースに対して処理を行う場合は問題となってしまう。プリフライトリクエストはこれをどうにかするためのリクエストのこと。
プリフライトリクエストの流れは以下の通り。
- ブラウザがOPTIONSメゾットで以下のヘッダーを含んだリクエストを送信
-
Access-Control-Request-Method
:送りたいリクエストのメゾット -
Origin
:リクエストが発生したオリジン
- サーバーは以下のヘッダーを含んだレスポンスを返す
-
Access-Control-Allow-Origin
:アクセスを許可するオリジン -
Access-Control-Allow-Methods
:通信を許可するメゾット -
Access-Control-Allow-Headers
:通信を許可するヘッダーの種類
- ブラウザはリクエストとレスポンスを比較してアクセス許可があるかチェック
- アクセス許可があれば実際のリクエストを送り、アクセス許可がなければ送信しない
実装する際は、上記の流れ2の
-
Access-Control-Allow-Methods
:通信を許可するメゾット -
Access-Control-Allow-Origin
:アクセスを許可するオリジン -
Access-Control-Allow-Headers
:通信を許可するヘッダーの種類
これらをOPTHONメゾットのレスポンスヘッダーに実装する必要がある。
単純リクエストになる(プリフライトリクエストにならない)条件
許可されているメソッドのうちのいずれかであること。
GET
HEAD
POST
ユーザーエージェントによって自動的に設定されたヘッダー (たとえば Connection、 User-Agent、 または Fetch 仕様書で禁止ヘッダー名として定義されているヘッダー)を除いて、手動で設定できるヘッダーは、 Fetch 仕様書で CORS セーフリストリクエストヘッダーとして定義されている以下のヘッダーだけです。
Accept
Accept-Language
Content-Language
Content-Type (但し、下記の追加の要件に注意してください)
Range (単純範囲ヘッダー値、例えば bytes=256- や bytes=127-255 の場合)
Content-Type ヘッダーで指定できるメディア種別に許されるタイプ/サブタイプの組み合わせは、以下のもののみです。
application/x-www-form-urlencoded
multipart/form-data
text/plain
この条件を見ると、POSTメゾットのリクエストでもContent-Type: application/json
だとフライトリクエストになるっぽい。CORSについて調べているとき、ここではまったという記事もいくつかあったので注意ポイント。
COREはなぜ必要になった?
- ブラウザは元々、同一オリジンのリクエストのみを受け付ける仕様だった
- AJAXの普及により、異なるオリジンのAPIを呼び出す必要が生まれた
ということでCORSは生まれたらしい。
まとめ
CORSとは、異なるオリジンのリソースへアクセスするための仕組み。
ブラウザ側の仕組みで、サーバーはヘッダーに情報を追加することでアクセス許可を出せる。
ヘッダーには以下の情報を追加する。
-
リクエストメゾットがGET,POST,HEADの場合:単純リクエスト
Access-Controll-Allow-Origin
-
リクエストメゾットがその他のメゾット(PUT,DELETEなど)の場合:プリフライトリクエスト
Access-Controll-Allow-Origin
Access-Controll-Allow-Methods
補足
オリジンとは、URLのスキーム(プロトコル)・ホスト(ドメイン)・ポート番号の組み合わせ
例)
URL:https://localhost/home/my_page:8080
↓
スキーム:https://
ホスト:localhost
ポート番号:8080
参考文献
Discussion