同一オリジンポリシー(SOP)とは
同一オリジンポリシー(SOP) とは、ブラウザ上で動作するスクリプトは、自身と同じオリジンを持つリソースに対してのみ自由にアクセスできる、という制約を課すセキュリティモデルです。
ここでいうオリジンは、次の3要素の組み合わせです。
- スキーム(プロトコル):
http,https - ホスト:
example.com,api.example.com - ポート:
80,443,8080など
したがって、オリジン = scheme://host:port となります。
具体例として、次のように判断されます。
-
https://example.comとhttps://example.com:443
→ スキームが https の場合、ポート 443 はデフォルトポートとして扱われるため 同一オリジン (http の場合はポート 80 がデフォルト) -
https://example.comとhttp://example.com
→ スキームが異なるため 別オリジン -
https://example.comとhttps://api.example.com
→ ホストが異なるため 別オリジン -
https://example.com:443とhttps://example.com:8443
→ ポートが異なるため 別オリジン
SOPが必要とされる背景
SOPが必要な理由は、ブラウザはユーザのCookieやログイン状態といった機密度の高い情報を保持しているためです。
仮にSOPが存在しなかった場合、XSSやCSRFなどの攻撃が極めて容易になってしまいます。
SOPが影響する範囲
SOPが影響するのは次のような場面です。
- DOM や
documentへのアクセス(別オリジンのiframeなど) -
fetchなどによるHTTPレスポンスの読み取り
いずれの場合も、「通信そのもの」ではなく、結果をスクリプトから読み取れるかどうかが制御されます。
代表的な具体例として、次のようなケースがあります。
- フロントエンドのSPA(例:
https://frontend.example.com)から、別オリジンのAPI(例:https://api.other.com)へfetchでリクエストを送る場合、- リクエスト自体は送信されるものの、サーバ側で適切なCORS設定が行われていなければ、レスポンスボディをJavaScriptから参照することはできない
- サイトA(例:
https://app.example.com)が、別オリジンのサイトB(例: YouTube)のiframeを埋め込む場合、- 見た目としては同じページ内に表示されるが、サイトAのスクリプトから、
iframe内のdocumentを直接読み書きすることはできない
- 見た目としては同じページ内に表示されるが、サイトAのスクリプトから、
同一オリジンと別オリジンの挙動の違い
厳密には例外も多く存在しますが、おおまかに整理すると次のようになります。
| 対象 | 同一オリジン | 別オリジン(SOP適用時) |
|---|---|---|
DOMアクセス(document など) |
読み書き可能 | 原則不可(別オリジン iframe の中身など) |
fetch 等のレスポンス読み取り |
本文をスクリプトから参照可能 | 原則不可(CORSで許可された場合のみ参照可能) |
localStorage / sessionStorage
|
当該オリジンのデータにアクセス可能 | 別オリジンのストレージにはアクセス不可 |
| Cookie | JSから参照可能(HttpOnly でなければ) |
リクエスト自体には自動で含めて送信されるが、JSから別オリジンのCookieは参照不可 |
window.opener / window.parent
|
アクセス可能(セキュリティには注意) | 制約あり(同一オリジンでない場合は操作が制限される) |
CORSとは
CORS(Cross-Origin Resource Sharing) とは、あるオリジン上で動作しているWebアプリケーションが、別のオリジンにあるリソースへ安全にアクセスできるようにするための仕組みです。
SOPによって本来はブロックされるクロスオリジンのレスポンス参照を、サーバ側の明示的な許可にもとづいて例外的に認めるためのプロトコルという位置付けになります。
CORSの基本的な考え方
ブラウザは、別オリジンへのリクエスト自体は送信するが、レスポンスをJavaScriptから参照してよいかどうかは、レスポンスヘッダでサーバが宣言した内容に従って判断するというのがCORSの基本的な考え方です。
このときサーバは、次のようなヘッダを用いて許可範囲を指定します。
-
Access-Control-Allow-Origin- どのオリジンからのリクエストを許可するか(例:
https://frontend.example.com)
- どのオリジンからのリクエストを許可するか(例:
-
Access-Control-Allow-Credentials- Cookie などの資格情報付きリクエストを許可するか
-
trueを返す場合、Access-Control-Allow-Origin: *のようなワイルドカード指定は使えず、特定のオリジンを明示する必要がある
-
Access-Control-Allow-Methods- 許可するHTTPメソッド(例:
GET, POST, OPTIONS)
- 許可するHTTPメソッド(例:
-
Access-Control-Allow-Headers- クライアントから送信を許可するリクエストヘッダ
シンプルリクエストとプリフライトリクエスト
ブラウザがCORSを扱う際、すべてのリクエストで一律にプリフライト(事前確認)の OPTIONS リクエストを送信しているわけではありません。挙動は大きく次の2種類に分類されます。
- シンプルリクエスト
- プリフライト付きリクエスト
シンプルリクエストとは
次の条件をすべて満たす場合、ブラウザは事前の OPTIONS リクエストを行わず、直接本来のリクエストを送信します。このリクエストはシンプルリクエストと呼ばれます。
- メソッドが
GET/HEAD/POSTのいずれかである - 送信するヘッダが、ブラウザが自動的に付与する一部の「安全な」ヘッダに限定されている
- 例:
Accept,Accept-Language,Content-Languageなど
- 例:
-
Content-Typeが次のいずれかであるtext/plainapplication/x-www-form-urlencodedmultipart/form-data
この場合でもレスポンスボディをJavaScriptから参照できるかどうかの判断はCORSレスポンスヘッダにもとづいて行われます。
プリフライトリクエストとは
上記の条件を満たさないリクエストについては、ブラウザは本来のリクエストを送信する前に、OPTIONS メソッドによる事前確認をサーバに対して行います。この事前確認用のリクエストが プリフライトリクエスト です。
例えば次のようなケースです。
-
PUT/PATCH/DELETEなどのメソッドを用いる場合 - カスタムヘッダ(例:
X-Requested-With,X-CSRF-Tokenなどの独自ヘッダ)を付与する場合 -
Content-Type: application/jsonでPOSTする場合
ブラウザは、プリフライトのレスポンスに含まれる次のヘッダを確認し、本来のリクエストを送信してよいかどうかを判断します。
Access-Control-Allow-MethodsAccess-Control-Allow-HeadersAccess-Control-Allow-Origin
ここでサーバが適切な値を返さない場合、本来のリクエスト自体が送信されない、あるいは送信されても結果がJavaScriptから参照できないといった挙動になります。
まとめ
- 同一オリジンポリシー(SOP)は、
scheme + host + portが同じオリジンの中だけで自由にリソースにアクセスできるというブラウザの基本ルール - 別オリジンのリソースに対してはDOMアクセス、レスポンス読み取り、ストレージアクセスなどが制限される特定の相手だけ例外的に許すための仕組み