Webアプリケーションにおけるクロスオリジン通信の実践的対策

同源ポリシーの基本メカニズム

ブラウザのセキュリティ制約により、異なるオリジン(スキーム・ドメイン・ポートの組み合わせ)への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
    );
  }
});

タグ: CORS JSONP postMessage document.domain iframe

6月1日 00:14 投稿