CORS(Cross-Origin Resource Sharing)って知ってはいるけど、やっぱりつまづいてイラつく人っているよね
僕もそう、過去何回かCORSについての記事は書いたことがある
- これとか(https://shiro-secret-base.com/%e5%a4%96%e9%83%a8%e3%81%aelaravel%e3%82%b5%e3%83%bc%e3%83%90%e3%81%ab%e5%af%be%e3%81%97%e3%81%a6cors%e3%82%a8%e3%83%a9%e3%83%bc%e3%82%92%e5%9b%9e%e9%81%bf%e3%81%97%e3%81%a6api%e3%83%aa%e3%82%af/)
- これとか(https://shiro-secret-base.com/%e3%81%84%e3%81%84%e3%81%8b%e3%81%92%e3%82%93%e3%83%9e%e3%82%a4%e3%82%af%e3%83%ad%e3%82%b5%e3%83%bc%e3%83%93%e3%82%b9%e3%81%aecors%e3%82%a8%e3%83%a9%e3%83%bc%e5%af%be%e7%ad%96%e3%82%92%e8%a6%9a/)
まあ似たような内容が若干被っているのはご愛嬌で、今回はMDNを読んだ内容(https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)をベースに再整理してみました
CORSとは
CORSとは同一オリジン以外へのリクエストをするときに、「お前のサービスのリソースではないと思うからリクエスト先に許可取っとくね」というブラウザのセキュリティ的な理由によるお節介です
許可が通らないとブラウザはリクエストの送信自体を行いません
同一オリジンブラウザが判断するのはMDNの同一オリジンポリシー(https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)によると
- プロトコル(http, https)が同じ
- ポート番号が同じ
- ホスト名が同じ
ということですなので
http://domain-a.com
からリクエストを送信する場合、以下のリソースへはCORSとしてブラウザに判断されます
https://domain-a.com
:プロトコルが違う(http ≠ https)http://domain-a.com:8080
:ポート番号が違う(80 ≠ 8080)http://domain-b.com
:ホスト名が違う(domain-a.com ≠ domain-b.com)http://hoge.domain-a.com
:サブドメインをつけてもダメ(domain-a.com ≠ hoge.domain-a.com)
CORSを通すには
いやいや、今時のWeb開発ではオリジンが違うサイトへのリクエストなんてしょっちゅう起きるでしょw
今時はモノリスじゃなくって、サーバーとフロントはドメインを分けてるもんでしょww
セキュリティ的な理由があるとはいえ同一オリジンポリシーは完全一致以外は認めないというのはなかなか辛い。個人的にはサブドメインくらいは許して欲しかった
といった現実があるので、CORSをちゃんと通すための方法を理解しておく必要性があるのです。
CORSにおいてブラウザはサーバへのリクエストから許可をもらうために
プリフライトリクエストというHTTPヘッダーのみのリクエストを送ります。
プリフライトリクエストに対してサーバから正常なレスポンスで返してくれたら、ブラウザはそのドメインへのリクエストを試みようとします。
プリフライトリクエストについて
では実際プリフライトリクエストについて説明しますと
フロントサイドとサーバサイドで取り決めたヘッダーをOPTIONSメソッドで送ることになります。
フロントサイドは自分が何者で何を許可してほしいのかを送る
- Origin:どこからリクエストを送信しているのか(例:https://example.com)
- Access-Control-Request-Method:どのリクエストメソッドを許可してほしいのか(GET, POST, PUT, DELETEなど)
- Access-Control-Request-Headers:どのリクエストヘッダーを許可してほしいのか
サーバサイドは誰に対して何を許可するのかを送る
- Access-Control-Allow-Origin:どこからのリクエストを許可するのか
- Access-Control-Allow-Methods:どのリクエストメソッドを許可するのか
- Access-Control-Allow-Headers:どのリクエストヘッダーを許可するのか
- Access-Control-Max-Age:どのくらいの時間許可するのか
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
OPTIONS /doc HTTP/1.1 Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Connection: keep-alive Origin: https://foo.example Access-Control-Request-Method: POST Access-Control-Request-Headers: X-PINGOTHER, Content-Type HTTP/1.1 204 No Content Date: Mon, 01 Dec 2008 01:15:39 GMT Server: Apache/2 Access-Control-Allow-Origin: https://foo.example Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-PINGOTHER, Content-Type Access-Control-Max-Age: 86400 Vary: Accept-Encoding, Origin Keep-Alive: timeout=2, max=100 Connection: Keep-Alive |
引用(https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
上のプリフライトリクエストの例だと
- Origin:https://foo.example
- Access-Control-Request-Method:POST
- Access-Control-Request-Headers:X-PINGOTHER, Content-Type
がフロントサイドが送るリクエストで
- Access-Control-Allow-Origin:https://foo.example
- Access-Control-Allow-Methods:POST, GET, OPTIONS
- Access-Control-Allow-Headers:X-PINGOTHER, Content-Type
- Access-Control-Max-Age:86400
とサーバサイドからレスポンスが204コードで返ってきていますので、プリフライトリクエストが通ったことになります。
認証情報込みのCORSの場合
CORSはCookieのような認証情報込みのリクエストになると、また一段と話がややこしくなります。
認証されたユーザ以外からのリクエストを極力弾きたいのでプリフライトリクエストの中身はもっとしっかりとした物にしたいといけないからです。
ではフロントサイドとサーバサイドでそれぞれどのようにすればいいのかというと
フロントサイド
- HTTPリクエストを送信するときにクレデンシャル(認証情報)を許可する
XMLHttpRequestを例に挙げると
1 2 3 4 5 6 7 8 9 10 11 |
const invocation = new XMLHttpRequest(); const url = "https://bar.other/resources/credentialed-content/"; function callOtherDomain() { if (invocation) { invocation.open("GET", url, true); invocation.withCredentials = true; // <= クレデンシャルを許可 invocation.onreadystatechange = handler; invocation.send(); } } |
引用(https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
サーバサイド
- Access-Control-Allow-Orignにワイルドカード
*
を使わずに厳密なオリジンを指定すること - Access-Control-Allow-Headersに
*
を使わずに明確なヘッダーを指定すること - Access-Control-Allow-Methodsに
*
を使わずに明確なメソッドを指定すること
となります。
まとめ
要するにCORSとは
- 同一ドメイン(プロトコル、ポート、ホスト)ではないリソースへのアクセスを制限するブラウザのセキュリティ
- プリフライトリクエストを正しく通せばOK
- 認証情報が絡んでいる場合だとプリフライトリクエストはしっかりとしないといけない
です。
今回も割と手抜き気味な内容になっちゃいましたが最後まで読んでいただいてありがとうございました。