5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

No.15 新卒未経験エンジニアがWebセキュリティの基本「SOP」を分かりやすく解説してみた〜第3章 CORS〜

Posted at

短いようで短かった、Webセキュリティの基本シリーズ最終章「CORS」です!
SOP(Same-Origin Policy)を回避する技術で、前回解説したJSONPを裏技とすると、CORSは表技になります。
なので、これからAPIを実装したり、利用したりする方はCORSを使うことをオススメします。

さぁ、始めましょう!

目次

第1章 SOP
第2章 JSONP
第3章 CORS → 今回はココ!

前回までの簡単なおさらい

簡単に前回までの簡単なおさらいをしておきます。
本当に簡単にしかしないので、詳しく知りたい方は目次のリンクから辿って読んでくださいね!

SOP(Same-Origin Policy)は異なるオリジンからのリソースへのアクセスを制限する、主要なブラウザに実装されているセキュリティの仕組みです。
こいつがいることで僕たちは色々助かっています。
しかし、APIなどで異なるオリジンのデータを取得して利用したい、ということもあり、SOPの制限をすり抜ける技術が生まれました。
「JSONP」と「CORS」です。
「JSONP」はscript要素はSOPの制限に引っかからないことを利用して、サーバーから返すデータをJSON形式のデータを引数にとった関数の形で返し、SOPの制限を受けずにデータを取得する技術です。
これは、SOPの抜け穴的な使い方なのでセキュリティ上問題があり、あまり推奨されていません。

というのが第1章、第2章の大まかなまとめです。
本当に大まかになりました笑

CORSって何??

では今回のテーマである、「CORS」を解説していきます。
CORSはCross Origin Resource Sharingの略で、文字通りクロスオリジンのリソースをシェアするための技術です。
はじめにも述べた通り、JSONPが裏技的な抜け道であるのに対してCORSは表の技術です。
CORSはW3C(The World Wide Web Consortium)という、HTML・XML・DOM・CSS・PNGなどの規格の標準化を行っているWebの標準化団体が推奨している方法なので、安心して利用出来ます!

CORSを簡単に説明すると、
・クライアント側では自分が何者かを説明する
・サーバー側では許可証を発行する
というようなことをします。

では実際にどんなことが行われているのか見てみましょう!

CORSはどんな仕組み!?

以下の3つのリクエストパターンに分けて見てみます。

■シンプルなリクエスト
■プリフライトリクエスト
■クレデンシャルを含むリクエスト

それぞれリクエストの投げ方の違いを見ながら解説していきます!

シンプルなリクエスト

シンプルなリクエストとは以下の条件に合うリクエストです。

・メソッド : GET・POST・HEAD
・(手動で設定出来る)リクエストヘッダ : Accept・Accept-Language・Content-Language・Content-Type
・Content-Typeヘッダに設定する値 : application/x-www-form-urlencoded・multipart/form-data・text/plain

クライアント側(ajax)
$.ajax({
  type: 'GET',
  url: 'http://abcde.com/get_data.php'
}).done(function(data){ /*・・・*/});
クライアント側(FetchApi)
fetch('http://abcde.com/get_data.php', {
  mode: 'cors'
}).then(function(response){ /*・・・*/});

typeをGET・POST・HEADのいずれかにし、リクエストを送る、至ってシンプルな形です。
Fetch APIの場合はオプションで「cors」と指定することで、CORSを使うことを宣言します。
ちなみにContent-Typeは設定しない場合、デフォルトでapplication/x-www-form-urlencodedになります。

この時、サーバー側からレスポンスにオリジンを超えたアクセスを許可する許可証を含めてあげる必要があります。

まず、ブラウザからサーバーに送られるHTTPリクエストヘッダには以下の内容が含まれます。

HTTPリクエストヘッダ
GET /get_data.php HTTP/1.1
Origin: http://example.com

このオリジンに対してアクセスを許可する場合、HTTPレスポンスヘッダに以下の内容を追加します。

HTTPレスポンスヘッダ
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://example.com

こうすることでhttp://example.comからのアクセスが許可され、データを取得することが出来ます。

なお、このようなシンプルなリクエストの時のみ、Access-Control-Allow-Originに「*(ワイルドカード)」を指定することが出来ます。
ですがその場合、全てのオリジンからのリソースへのアクセスを許可することになるのでセキュリティ上あまり良くありません。
なるべく使わない方が良いでしょう。

サーバー側ではこんな感じで実装するイメージです。

get_data.php
$allowed_origin = 'http://example.com';

if ($_SERVER['HTTP_ORIGIN'] == $allowed_origin) {
    session_start();

    header("Access-Control-Allow-Origin: $allowed_origin");

    $response = array('is_login' => isset($_SESSION['user_id']));
} else {
    $response = array('error' => '未対応のサービスです');
}

header("Content-Type: application/json; charset=utf-8");
echo json_encode($response);

プリフライトリクエスト

プリフライトリクエストは、ボクシングでいうところのジャブです。
いけるかどうか試しにまずは打ってみる、ということです。
最初に実際のリクエストを送信しても安全かを確かめるために異なるオリジンのリソースへ向けて試しにHTTPリクエストを送信します。
返ってきた結果を元に、本チャンのリクエストを送るという流れになります。

クロスオリジンリクエストはユーザーデータに影響を与える可能性があるため、このようにプリフライトリクエストを行います。

以下の条件に一つでも当てはまる時に、プリフライトリクエストは行われます。

■メソッド : GET・POST・HEAD以外のメソッド
■(手動で設定出来る)リクエストヘッダ : Accept・ Accept-Language・Content-Language・Content-Type以外のフィールドが含まれている
■Content-Typeヘッダに設定する値 : application/x-www-form-urlencoded・multipart/form-data・text/plain以外の内容が指定されている

この場合、HTTPリクエストヘッダには以下の内容が含まれます。

HTTPリクエストヘッダ
OPTIONS /get_data.php HTTP/1.1
Access-Control-Request-Method: (この後に行うリクエストのメソッド(GET, POSTなど))

サーバー側ではOPTIONSリクエストがきた時の対応もしておく必要があります。
レスポンスとしては例えば、少なくともオリジンを越えるアクセスとして許可するHTTPリクエストのメソッドを指定する必要があります。

HTTPレスポンスヘッダ
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://example.com
Access-Control-Allow-Methods: GET,POST,HEAD,OPTIONS

このプリフライトリクエストを知らないと色々困ることが出てくる可能性があります。
それはこのプリフライトリクエストもリクエストの1回として認識されてしまうからです。
つまり通常の2倍の数のリクエストが行われるということです。
例えば、リクエスト数に応じて課金される場合、想定の2倍の料金になってしまいます!!
また、APIを提供する側ではサーバーの負荷も2倍のリクエスト数で考えておかないといけません!!

知らないと怖いプリフライトリクエスト。。。

クレデンシャルを含むリクエスト

クレデンシャルとは、CookiewやHTTP認証情報のことです。
ようはリクエストを投げる時に自分が何者かをサーバー側に提示します。

デフォルトではクレデンシャル情報の送受信をしないような設定になっているので、それを変更してあげる必要があります。

クライアント側(ajax)
$.ajax({
  type: 'GET',
  url: 'http://abcde.com/get_data.php',
  xhrFields: {
    withCredentials: true
  }
}).done(function(data){ /*・・・*/});
クライアント側(FetchApi)
fetch('http://abcde.com/get_data.php', {
  mode: 'cors',
  credentials: 'include'
}).then(function(response){ /*・・・*/});

ajaxではwithCredentialsプロパティをtrueにしてあげる必要があります。
fetch apiの場合は上記のようにします。

これで安心!!
ではありません!
サーバー側からのレスポンスヘッダに
Access-Control-Allow-Credentials: true
という内容を追加してあげる必要があります。

HTTPレスポンス
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://example.com
Access-Control-Allow-Credentials: true

クレデンシャルを送受信する場合、Access-Control-Allow-Originに「*(ワイルドカード)」は使えないので注意してください。

まとめ

少し長くなりましたが、以上がCORSです。
JSONPに比べると随分正攻法な感じがしますね笑

個人的に一番知れてよかったと思ったのはプリフライトリクエストについてです。
このまま知らずに色々実装していたらもしかした大失敗してしまう可能性がありましたね。。。

こういった、裏で動いている仕組み的なことを学んでおくことが大事だということが分かりました!

これでWebセキュリティの基本「SOP」編は終了です。
また次回別シリーズでお会いしましょう!!

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?