こんにちは 、ソリューション技術部の木下です。
ベンチプレス100kgは上げられるものの105kgが一向に上げられず、減量中のせいだということにして、やり過ごしている日々です。きっと増量し始めたら上げられると信じています。
さて、CORSはWeb開発を行っている方はほとんどの方が遭遇したことがあるかと思いますが、初見だとなかなか対応方法がわからない、あるいは設定方法だけはわかったけどなぜ必要なのか、何をしているのかわからないという新人の方も多いのではないでしょうか。
そんな方にお送りする「CORS入門:初心者が知るべき基本について」について、お送りします。
- CORS (Cross-Origin Resource Sharing)の概略
- SOPが必要な理由
- 改めてCORSとは
- CloudFront経由でS3に置いているJSONファイルを取得する場合のCORS設定
- まとめ
- 参考情報
CORS (Cross-Origin Resource Sharing)の概略
特定の条件下でCross-Originなリソースへのアクセスを許可する仕組みです。
なぜ許可が必要か
Webのセキュリティポリシーである「Same-Origin Policy(以下、SOPと記載する)」として、同一Originのリソースのみが互いに自由に通信できると定められていますが、実際には異なるOriginからのリソースが必要な場面も多く存在するためです。
以下、簡単に用語について、理解を深めていきましょう。
Originとは
スキーム, ホスト名, ポート番号の3つの組として定義されます。いくつかのURIをもとにそれぞれがどの値に当たるのかを表にしました。
表1. Originについて
No | URI | スキーム | ホスト | ポート番号 |
---|---|---|---|---|
1 | https://origin1.example.com/ |
https | origin1.example.com | 443 |
2 | https://origin1.example.com/bbb |
https | origin1.example.com | 443 |
3 | https://origin2.example.com/ |
https | origin2.example.com | 443 |
4 | http://localhost:3000/ |
http | localhost | 3000 |
Cross-Originとは
2つのWebリソースのOriginが異なることを指します。 表1のNo1とNo3、No2とNo3などはCross-Originです。
Same-Originとは
2つのWebリソースのOriginが同じであることを指します。 表1のNo1とNo2はSame-Originです。
SOPが必要な理由
そもそもSame-OriginでもCross-Originでも制限なく通信ができれば、CORSは必要ありません。
なぜ、SOPが必要なのでしょうか。
Webブラウザはインターネット上のウェブページやウェブサイトを表示するためのソフトウェアで、Webサーバなどと通信を行なった結果を処理・表示しています。 インターネット上には悪意のあるものが存在しており、以下いくつかの例を記載します。
特定のリソースを示すURLにDELETEメソッドのリクエストを送信することで、そのデータを削除することができますが、悪意のある人が用意したWebリソースから他のWebリソースにこのリクエストを発行できると、ユーザはデータを失ってしまう可能性があります。
MDNのSOPの説明にも「インターネット上の悪意のあるウェブサイトがブラウザー内で JS を実行して、 (ユーザーがサインインしている) サードパーティのウェブメールサービスや (公開 IP アドレスを持たないことで攻撃者の直接アクセスから保護されている) 企業のイントラネットからデータを読み取り、そのデータを攻撃者に中継することを防ぎます。」と記載があります。*1
つまり、何の制限もなくWebリソースへ操作ができてしまうことで発生する問題があるため、ある程度の制限を加える必要が出てきます。
では、どのような制限を設けるのが適切でしょうか。
答えはすでに出てきているように「同一Originのリソースのみが互いに自由に通信できる」ように制限がされていますので、同一Origin(スキーム, ホスト名, ポート番号の3つの組が同じ)かどうかで制限をかけるのが良いという結論になります。
しかし、ホストだけ一致していれば安全なんじゃないの?スキームとホストが一致していれば安全なんじゃないの?と思ったりしませんか。
スキーム不一致の場合
スキームが一致していなくても良い場合、HTTP通信ではデータが平文で送信されるため、中間者攻撃によるデータの傍受が可能であるため、異なるスキーム間でリソースを共有することは望ましくありません。
ホスト不一致の場合
ホストが一致していなくても良い場合、一方のホストから別のホストのCookieやストレージにアクセスができ、ユーザの認証情報や機密情報が危険に晒される可能性があります。
ポート番号不一致の場合
上記のようにスキームとホストは少なくとも一致していたほうが良さそうです。
ポート番号についてもhttp://localhost:3000
とhttp://localhost:5000
のようにポート番号が異なる場合、通常別のWebアプリケーションであることを指します。制限がない場合、異なるアプリケーション間で互いのデータにアクセスができると意図しない情報漏洩やデータの不正利用が発生する可能性があります。
以上の理由から、リソース間の操作の制限を「スキーム、ホスト、ポート番号が一致しているリソースであること」、つまりそれらを「Origin」として定め、Originを基準として制限するためにSOPが存在しています。
改めてCORSとは
SOPによって、異なるOriginとの通信が制限されますが、悪意なく、正規の通信として異なるOrigin間で通信を行いたいケースがあります。
例えば、自社間で開発した2つのWebサービスを連携するためにWebサービス:https://example.example.com
から別のWebサービスのAPI: https://api.example.com
をFetch APIを利用してデータを取得したいというような場合です。
そういったケースを叶えるため、CORSが存在しています。
冒頭の概略に記載しましたが、CORSとはSOPという制限の中で「特定の条件下でCross-Originなリソースへのアクセスを許可する仕組み」です。
では、特定の条件下とはどういったものでしょうか。
異なるOriginでアクセス許可するための特定条件とは
CORSの設定には主に以下3つのケースが存在します。
基本的には以下の条件に合致する場合にシンプルリクエストと判断されます。
- HTTPメソッドがGET, HEAD, POSTの場合
- 手動設定したHTTPヘッダーがAccept、Accept-Language、Content-Language、Content-Typeのみの場合
- Content-Typeはapplication/x-www-form-urlencoded、multipart/form-data、text/plainのいずれかの場合
- CookieやHTTP認証といった資格情報を含まない
XMLHttpRequestを利用した場合など、一部のケースで上記条件に合致してもシンプルリクエストと判断されないケースがありますので、詳細は脚注*1をご確認いただければと思います。
リクエストの種類ごとに満たすべき特定条件について、表2にまとめました。
表2. CORSを許可する特定条件について
種類 | リクエスト | レスポンス |
---|---|---|
シンプルリクエスト | ・Originヘッダーにリクエスト発行元の値が指定されている | ・Access-Control-Allow-Originヘッダーにリクエスト発行元のOrigin、または * (全てのOrigin)が指定されている |
プリフライトリクエスト | ・Originヘッダーにリクエスト発行元の値が指定されている ・Access-Control-Request-Methodヘッダーに発行したいリクエストのメソッドを値として指定されている ・Access-Control-Request-Headersヘッダーに発行したいリクエストに付与したいヘッダー名のリストが指定されている |
・Access-Control-Allow-Originヘッダーにリクエスト発行元のOriginが指定されている ・Access-Control-Allow-Methodsヘッダーに発行したかったリクエストメソッドが指定されいてる ・Access-Control-Allow-Headersヘッダーに発行したかったリクエストのヘッダーが指定されている ・Access-Control-Max-AgeヘッダーにWebブラウザにキャッシュさせたいキャッシュ可能秒数が指定可能(必須ではない) |
資格情報を含むリクエスト | ・Originヘッダーにリクエスト発行元の値が指定されている | ・Access-Control-Allow-OriginヘッダーにリクエストヘッダーのOriginヘッダーに指定された値を含んでいる ・Access-Control-Allow-Credentialsヘッダーにtrueが指定されている |
※ リクエストのOriginヘッダーについてはCross-Originなリクエストの場合にブラウザが自動でOriginヘッダーを付与するため、開発者側で設定することはできません。*5
実際に確認してみる
実際にそれぞれの場合について、詳細に見ていきましょう。
リクエスト発行元Webサービス: http://localhost:5000/
からAPI: http://localhost:8000/json
に対して、リクエストします。
APIは以下のようにJSON形式でmessageを返却するものとします。
$ curl http://localhost:8000/json {"message":"Hello, world!"}
シンプルリクエスト
Access-Control-Allow-Originヘッダーに適切な値が設定されていない場合、ブラウザ上では以下のようなエラーが表示されます。
一方、Access-Control-Allow-Originヘッダーに適切な値を設定した場合、リクエストが成功します。
プリフライトリクエスト
プリフライトリクエストのレスポンスにて、必要なヘッダーであるAccess-Control-Allow-MethodsヘッダーとAccess-Control-Allow-Headersヘッダーが設定されていない場合、以下のようなエラーが表示されます。
プリフライトのリクエストに必要なヘッダーを設定していても、本来発行したかったリクエストのレスポンスにAccess-Control-Allow-Originヘッダーが設定されていない場合、以下のようにエラーが出ます。
どちらも適切にヘッダーを設定した場合、リクエストが成功します。
資格情報を含むリクエスト
資格情報を含むリクエストにて、レスポンスのAccess-Control-Allow-Originヘッダーは適切に設定されているが、Access-Control-Allow-Credentialsヘッダーが設定されていない場合、以下のようにエラーになります。 Access-Control-Allow-Originヘッダーに適切な値が含んでいない場合はシンプルリクエストと同様のエラーになりますので割愛します。
Access-Control-Allow-Credentialsヘッダーを適切に設定すると、以下のようにリクエストが成功します。
CloudFront経由でS3に置いているJSONファイルを取得する場合のCORS設定
これまでの話を踏まえて、S3に存在するJSONファイルをCloudFront経由で取得する場合のCORS設定について、簡単に紹介します。
※ 実際の使用用途等によって、適切な設定は変わります。
以下は、Webサービス:https://test.example.com
からFetch APIなどで異なるOriginのJSONファイル: https://test-static.example.com/test.json
を取得することを想定した図になります。
① CloudFront オリジンリクエストの設定
オリジンリクエストポリシーに「CORS-S3Origin」を設定します。
この「CORS-S3Origin」はオリジン(S3)へのリクエストにOrigin、 Access-Control-Request-Methodヘッダー、Access-Control-Request-Headersヘッダーを転送します。
② S3 Cross-Origin Resource Sharing (CORS)の設定
次に、S3のCORS設定にて、以下のように許可するヘッダー、メソッド、Originなどを設定します。
[ { "AllowedHeaders": [ "*" ], "AllowedMethods": [ "GET" ], "AllowedOrigins": [ "https://test.example.com" ], "ExposeHeaders": [] } ]
設定箇所がCloudFrontやS3と分かれてはいますが、
結局のところ、Cross-Origin通信に必要なリクエストヘッダーとレスポンスヘッダーを設定しているだけに過ぎません。
まとめ
この記事ではCORSについて、記載しました。 CORSに悩む人の助けに少しでもなれば幸いです。
また、CORSはSOPの制限を突破して、適切に異なるOrigin間で通信できるようにするための仕組みの一つです。 SOPの制限を突破するための方法はCORSだけではありませんし、WebブラウザセキュリティはSOPだけでもありません。 その他のWebブラウザセキュリティに興味が出てきたら、色々と調べてみるのも良いかと思います。
参考情報
Webブラウザセキュリティ ― Webアプリケーションの安全性を支える仕組みを整理する – 技術書出版と販売のラムダノート
Origin (オリジン) - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN
オリジン間リソース共有 (CORS) - HTTP | MDN
*1:同一オリジンポリシー: https://developer.mozilla.org/ja/docs/Web/Security/Same-origin_policy
*2:シンプルリクエスト: https://developer.mozilla.org/ja/docs/Web/HTTP/CORS#%E3%83%97%E3%83%AA%E3%83%95%E3%83%A9%E3%82%A4%E3%83%88%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88
*3:プリフライトリクエスト: https://developer.mozilla.org/ja/docs/Web/HTTP/CORS#%E3%83%97%E3%83%AA%E3%83%95%E3%83%A9%E3%82%A4%E3%83%88%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88
*4:資格情報を含むリクエスト: https://developer.mozilla.org/ja/docs/Web/HTTP/CORS#%E8%B3%87%E6%A0%BC%E6%83%85%E5%A0%B1%E3%82%92%E5%90%AB%E3%82%80%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88
*5:Originリクエストヘッダー: https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Origin#%E8%A7%A3%E8%AA%AC