同源ポリシーの基本メカニズム
ブラウザのセキュリティ制約により、異なるオリジン(スキーム・ドメイン・ポートの組み合わせ)へのXMLHttpRequest/fetch要求がブロックされる現象を指します。オリジンの一致判定は以下の通りです:
| リクエスト元URL | リクエスト先URL | 通信可否 |
|---|---|---|
| https://app.example.co.jp/main.js | https://app.example.co.jp/api.js | 許可 |
| https://app.example.co.jp:8080/main.js | https://app.example.co.jp/main.js | ブロック(ポート不一致) |
| https://app.example.co.jp/main.js | http://app.example.co.jp/main.js | ブロック(プロトコル不一致) |
| https://app.example.co.jp/main.js | https://api.example.co.jp/data.js | ブロック(サブドメイン不一致) |
主要な対策手法
JSONPによるデータ取得
<script>タグは同源ポリシーの適用外のため、動的スクリプトインジェクションでデータを取得します。サーバー側で指定のコールバック関数を実行するスクリプトを返却します。
<!-- クライアント側(client.html) -->
<script>
function handleExchangeRate(response) {
console.log(`為替レート: ${response.base} → ${response.target}: ${response.rate}`);
}
</script>
<script src="https://api.example.co.jp/rate?callback=handleExchangeRate"></script>
// サーバー側(api.example.co.jp/rate)
handleExchangeRate({
"base": "USD",
"target": "JPY",
"rate": 145.3
});
CORSヘッダーの設定
サーバー側でレスポンスヘッダーにAccess-Control-Allow-Originを指定します。
// Node.jsサーバー例
app.get('/data', (req, res) => {
res.header('Access-Control-Allow-Origin', 'https://client.example.co.jp');
res.header('Access-Control-Allow-Methods', 'GET,POST');
res.json({ status: 'success' });
});
document.domainによるサブドメイン間通信
共通のメインドメインを持つサブドメイン間で、ドメイン境界を緩和する方法です。
<!-- 親ページ(main.example.co.jp/page.html) -->
<iframe src="https://sub.example.co.jp/child.html" id="frame"></iframe>
<script>
document.domain = 'example.co.jp';
const frame = document.getElementById('frame');
frame.onload = () => {
console.log(frame.contentWindow.sharedData);
};
</script>
<!-- 子ページ(sub.example.co.jp/child.html) -->
<script>
document.domain = 'example.co.jp';
window.sharedData = { token: 'auth_token' };
</script>
postMessageによる安全なメッセージング
異なるオリジンのウィンドウ間で安全にデータを交換できます。
// 親ウィンドウ
const childWindow = window.open('https://external.example.co.jp');
childWindow.postMessage(
{ type: 'handshake', payload: 'init' },
'https://external.example.co.jp'
);
window.addEventListener('message', (e) => {
if (e.origin !== 'https://external.example.co.jp') return;
console.log('受信データ:', e.data);
});
// 子ウィンドウ
window.addEventListener('message', (e) => {
if (e.data.type === 'handshake') {
e.source.postMessage(
{ status: 'connected' },
e.origin
);
}
});