
CORS(Cross-Origin Resource Sharing)同一生成元ポリシーによる外部リソースを共有する方法
CORS(Cross-Origin Resource Sharing)とは、本来同一生成元ポリシー(セキュリティポリシー)によるリソース利用制限を一部解除する手段です。
ブラウザとサーバーとが通信をするHTTPヘッダーに追加する事で、同一生成元の範囲に入れるか入れないかを設定できます。
HTTPヘッダーが変更された場合にはプリフライトリクエストが可能ですし、その情報を一定期間保存する事もできます。
CORSとは
CORSとはCross-Origin Resource Sharingの略語です。
CORSは、ブラウザがWEBサイトの実データが存在するサーバー以外のWEBサーバーからデータを取得する仕組みです。
ただどこからでも何でも手軽にデータが取得できる訳ではありません。
セキュリティ攻撃を防ぐ
基本的にWEBブラウザには、クロスドメイン通信を拒否する仕組みが実装されています。
プログラムコンポーネントで外部サイトデータを取得する事に制限を掛けないと、XSS(クロスサイトスクリプティング)の被害が発生するためです。
いつも利用しているWEBサイトで個人情報を入力したつもりが偽サイト内で入力をしてしまい、自分の個人情報が盗まれる被害につながるのです。
同一生成ポリシー
こういったXSSによるセキュリティ攻撃を防止するために、ブラウザは同一生成元ポリシー(Same-Origin Policy)という仕組みを実装しています。
通常WEBサーバーは、外部サイトプログラムによるデータ表示を要求するアクセスには応答しない仕様になっています。
CORS (Cross-Origin Resource Sharing)はこの制約を一部解除して、異なるサイト間でリソースを共有するための仕組みです。
同一生成元ポリシー(セキュリティポリシー)とは
同一生成元ポリシーは、別の生成元からのリソースの読み取りをブロックするブラウザに導入された制約です。
例えばサーバーA内のドキュメント(WEBページなど)はサーバーA内の他のドキュメントとのみ共有できます。
つまり同一生成元ポリシーは、共有するドキュメントが常に「同じサイト内にある事」を強制するものと言えます。
問答無用にブロックするセキュリティポリシー
これは外部サイトが別サイトのデータを悪意ある目的で利用するのを阻止するのに便利です。
しかし時には正当な理由で外部リソースを利用したいケースもあります。
例えば別ドメインからJSONデータを取得したり、別のサイトからcanvas要素に画像を読み込んだりするケースなどですね。
あえて自由に使って構わないリソースとして用意されたものでも、同じ生成元でない限りブロックされてしまいます。
CORSが同一生成元ポリシーの調整をする標準方法へ
以前まではJSONPなどで回避をしていましたが、CORSの導入によりこの同一生成元ポリシーの修正を標準的な方法で実現できる様になりました。
CORSを有効にすれば、サーバーはブラウザに追加で許可された「生成元」を通知する事ができます。
この生成元に追加されたドメインは「同一生成元」と判断されるので、外部サイトであってもそのリソースを共有できる訳ですね。
生成元(オリジン)の識別構成
生成元(以下:オリジン)は、主に以下の3つの項目から識別をしています。
・プロトコル
・ホスト
・ポート番号
プロトコル
プロトコルとは、インターネット上のリソースにアクセスするために使用されるスキーム名です。
スキーム名として最も一般的に使用されるプロトコルとしては以下の4つです。
・http://
・https://
・ftp://
・mailto://
それぞれ馴染みのあるものばかりですね。
ホスト名
ホスト名とは、リソースが配置されているホストサーバーに割り当てられたドメイン名の事です。
これは通常、ホストのローカル名とその親ドメインの名前の組み合わせで示します。
例えば「www.llpeg.info」であれば、ホストローカル名がwwwで、ドメイン名がllpeg.infoで構成されます。
ポート番号
ポートはアプリケーションが実行される通信エンドポイントです。
スキーム、ホスト名、ポートのこれら3つの組み合わせが同じ場合、ブラウザはそのリクエストを「同じオリジン」からの要求として識別します。
そしてそのリクエストが許可される訳ですね。
生成元(オリジン)を許可する例・しない例
現在は、これら個々の項目を全て詳細に指定する必要はありません。
しかし同一生成元ポリシーがこれらの部分をどのように使用するのかについては、指定する必要があります。
識別を許可する例
例えば、https://llpeg.info/test.htmlから、https://llpeg.info/contents.htmlに移動したとします。
この時各URLのプロトコル(HTTPS)、ホスト(llpeg.info)、およびポート(80)はそれぞれ一致するためページ移動が許可されます。
ポート80がデフォルトのポートです。
同一生成元ポリシーでは、この様に送信元の全ての部分が一致する必要があります。
識別を許可しない例
ではhttps://llpeg.info/test.htmlから、http://travel.llpeg.info/aaa.htmlへ移動するとなった場合はどうでしょう。
プロトコル(HTTP)も違いますし、ホスト(travel.example.com)も違いますので、移動が許可されない事になりますね。
標準では厳しすぎるセキュリティポリシー
確かにセキュリティポリシーがないとリスクを伴う可能性がある訳ですが、同一生成元セキュリティポリシーは制限が厳しすぎる点が問題でした。
これを受けて、クロスオリジンのように両方を組み合わせた少し緩めのセキュリティポリシーが誕生しました。
これがクロスオリジンリソース共有の標準的な方法として進化し、今はCORSと略されるようになっています。
CORSによるヘッダー通信の流れ
共有許可が出ている事をブラウザに知らせる
別の生成元からパブリックリソースを取得・共有する場合の前提事項があります。
リソースを提供する側のサーバーは「リクエストを要求している側のサーバーが、リソースにアクセスしても良い許可が出ている」事を、前もってブラウザに伝えておかなければなりません。
ブラウザはそれを記憶して、クロスオリジンのリソース共有を許可する訳です。
申請・許可等のやり取りは全てヘッダーへ記述
ステップ1:クライアント(ブラウザ)リクエスト
ブラウザがクロスオリジンリクエストを行うと、ブラウザは現在のオリジン(スキーム、ホスト、ポート)を含むヘッダーを追加します。
ステップ2:サーバーの応答
サーバー側がこのヘッダーを確認します。
ここでアクセスを許可(Access-Control-Allow-Origin)する場合、要求元を指定するヘッダーを応答に追加する必要があります。
ステップ3:ブラウザが応答を受け取る
ブラウザは適切なAccess-Control-Allow-Originヘッダーを持つこの応答を確認すると、応答データをクライアントサイトと共有できるようにします。
Access-Control-Allow-Origin
HTTPヘッダーは、要求または応答に関連付けられた情報の一部です。
WEBページが別のサーバーでホストされているリソースを要求する場合、WEBブラウザとサーバーの間ではこのhttpヘッダーを通じてやり取りがされます。
この時ヘッダーは要求と応答を記述するために使用されます。
CORSによって追加されるHTTPヘッダー
CORSは新しいHTTPヘッダーをヘッダーの標準リストに追加する事で、そのリクエストを管理しています。
CORSによって追加される新しいHTTPヘッダーには以下の様な種類があります。
HTTPヘッダーへ追加される種類
Access-Control-Allow-Origin
Access-Control-Allow-Credentials
Access-Control-Allow-Headers
Access-Control-Allow-Methods
Access-Control-Expose-Headers
Access-Control-Max-Age
Access-Control-Request-Headers
Access-Control-Request-Method
Origin
これらはすべて重要ですが、中でも注目すべきは次のヘッダーです。
Access-Control-Allow-Origin
Access-Control-Allow-Originヘッダーは、サーバーが対象のリソースを外部ドメインと共有する方法を指定する事ができるリストです。
例えば、サーバーAのリソースにアクセスする要求が行われると、サーバーAはAccess-Control-Allow-Originヘッダーの値で応答します。
多くの場合、この値は「*」になります。
これによりサーバーAは、要求されたリソースをインターネット上のあらゆるドメインと共有する許可をした事になります。
限定されたドメインのみにする場合
又このヘッダーの値により、許可をする先が特定のドメイン(またはドメインのリスト)に限定されている場合もあります。
この場合サーバーAは、そのリソースをその特定のドメイン(複数ドメインのリスト)とのみ共有する事になります。
このようにAccess-Control-Allow-Originヘッダーは、リソースのセキュリティにとって非常に重要な項目です。
プリフライトリクエスト
ほとんどのサーバーはGETによる要求を許可しますが、当然要求をブロックする場合もあります。
その際、毎回直接サーバーへ通信をおこなって確認をする訳ではありません。
どのリクエストが許可されているかを事前にチェックしてからクライアント(あなたのWEBブラウザ)と通信するプロセスを備えています。
HTTPリクエストに応じた事前チェック
以下のいずれかを使用したリクエストが行われると、事前チェックとなる「プリフライトリクエスト」が本来の要求の前に行われます。
HTTPリクエストメソッドの種類
PUT
DELETE
CONNECT
OPTIONS
TRACE
PATCH
プリフライトリクエストの仕組み
プリフライトリクエストは元のリクエストの前に送信されるため、飛び立つ前の準備という意味で「プリフライト」という用語が使われます。
プリフライトリクエストの目的は、元のリクエストが安全かどうかを判断する事です。
サーバーが元の要求が安全であると判断した場合、サーバーは本来のリクエストを許可します。
それ以外の場合は元のリクエストをブロックする訳です。
ヘッダーが変更された時も実施
プリフライトリクエストを実施するのは、上記のリクエストメソッドだけではありません。
ブラウザ(つまりユーザーエージェント)によって自動的に設定されたヘッダーのいずれかが変更された場合も、プリフライトリクエストが実施されます。
プリフライトリクエストによるヘッダー送信の例
ブラウザは必要に応じてプリフライトリクエストを実施します。
これは以下のようなOPTIONSのリクエスト時であり、実際のリクエストメッセージの前に送信されます。
ヘッダー送信例
OPTIONS /data HTTP/1.1
Origin: https://llpeg.info
Access-Control-Request-Method: DELETE
サーバー応答例
サーバー側では、アプリケーションはプリフライトリクエストに応答して、アプリケーションがこのオリジンから受け入れるメソッドに関する情報を返す必要があります。
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://llpeg.info
Access-Control-Allow-Methods: GET, DELETE, HEAD, OPTIONS
プリフライト結果のキャッシュ
この時サーバー応答には「Access-Control-Max-Age」など、プリフライト結果をキャッシュする期間(秒単位)を指定するヘッダーを含める事もできます。
クライアントが複雑な要求を送信する度にキャッシュを確認すれば良いので、毎回プリフライトリクエストをおこなわずに済みます。
CORS設定の確認方法
CORSを正しく設定するためのリクエストヘッダーの実装は、バックエンドの言語とフレームワークによって異なります。
NodeJSサーバーの場合
たとえばNodeJSを使用している場合、まずはnpmパッケージをインストールします。
npm install cors
次にサーバーアプリケーションに移動して以下のコードを追加します。
var cors = require( 'cors'); cors app.use(cors( { origin: "*"、 methods: "GET、HEAD、PUT、PATCH、POST、DELETE"、 preflightContinue:false }) );
上記ではorigin:で "*"としています。つまりどのドメインでもこのアプリケーションにアクセスができます。
Expressを使用している場合
Expressを使用している場合は、CORSミドルウェアで確認ができます。
$ npm install cors
記述例(server.js)
const express = require('express'); const app = express(); // CORSヘッダーを付けない app.get('/', function(request, response) { response.sendFile(__dirname + '/message.json'); }); // 下記ディレクトリ名の場合はCORSヘッダーを付ける app.get('/allow-cors', function(request, response) { response.set('Access-Control-Allow-Origin', '*'); response.sendFile(__dirname + '/message.json'); }); //ポート番号関連 const listener = app.listen(process.env.PORT, function() { console.log('Your app is listening on port ' + listener.address().port); });
参照元:Expressデモはこちら
上記のExpressデモページをGoogleChromeで開いて、F12を押してデベロッパーツールを表示させましょう。
「コンソール(Console)」の欄を開きます。
そこに、fetch('https://cors-demo.glitch.me/', {mode:'cors'})と入力してみましょう。
次の様なエラーが表示されます。
request has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
つまりCORSによる共有許可が出ていない訳ですね。
次は、fetch('https://cors-demo.glitch.me/allow-cors', {mode:'cors'})と入力してみましょう。
Access-Control-Allow-Originヘッダーが付いて許可されているため、エラーは出ません。
まとめ
あらゆるWEBテクノロジーに対して、リソースを共有できるソリューションが数多く存在しています。
ただし全体的な概念は常に同じです。悪用されるリスクは避けなければなりません。ですので何でも制限なしに利用できるのはよくないのです。
外部リソース利用に関するCORSの様なセキュリティポリシーを理解する事で、一定のルールに基づいた安全な利用を心掛けましょう。