API プロキシへの CORS サポートの追加

CORS(クロスオリジン リソース シェアリング)は、ウェブページで実行される JavaScript XMLHttpRequest(XHR)呼び出しがオリジン以外のドメインのリソースとやり取りできるようにする標準メカニズムです。CORS は、すべてのブラウザに組み込まれている「同一オリジン ポリシー」に対する一般的な解決策です。たとえば、ブラウザで実行されている JavaScript コードから Twitter API への XHR 呼び出しは失敗します。その理由は、ブラウザに該当ページをサービスしているドメインが、Twitter API をサービスしているドメインと同じでないからです。CORS はこの問題を解消すべく、サーバーがクロスオリジンでリソースを共有したい場合に、サーバーが「オプトイン」できる(承諾を得られる)ようにします。

動画: API プロキシで CORS を有効にする方法の詳細の短い動画をご覧ください。

一般的な CORS 使用例

次の JQuery コードは、架空のターゲット サービスを呼び出します。この呼び出しをブラウザ(Web ページ)のコンテキストから実行すると、同一オリジン ポリシーを理由に失敗します。

<script>
var url = "http://service.example.com";
$(document).ready(function(){
  $("button").click(function(){
    $.ajax({
        type:"GET",
        url:url,
        async:true,
        dataType: "json",
           success: function(json) {
              // Parse the response.
              // Do other things.
           },
           error: function(xhr, status, err) {
              // This is where we end up!
            }
    });
  });
});
</script>

この問題に対する解決策の 1 つとして、サービス API をバックエンドで呼び出す Apigee API プロキシを作成する方法があります。Edge は、クライアント(この場合はブラウザ)とバックエンド API(サービス)の間に配置されます。API プロキシはブラウザではなくサーバーで実行されるため、サービスを正常に呼び出すことができます。そのため、必要な操作は、CORS ヘッダーを TargetEndpoint レスポンスに接続することのみです。ブラウザで CORS がサポートされていれば、これらのヘッダーがブラウザに同一オリジン ポリシーを「緩和」しても問題ないことを知らせ、クロスオリジン API 呼び出しが正常に動作するようになります。

CORS をサポートするプロキシを作成したら、クライアント側コードでバックエンド サービスの代わりに API プロキシ URL を呼び出すことができます。例:

<script>
var url = "http://myorg-test.apigee.net/v1/example";
$(document).ready(function(){
  $("button").click(function(){
    $.ajax({
        type:"GET",
        url:url,
        async:true,
        dataType: "json",
           success: function(json) {
              // Parse the response.
              // Do other things.
           },
           error: function(xhr, status, err) {
              // This time, we do not end up here!
            }
    });
  });
});
</script>

新しい API プロキシへの Add CORS ポリシーの接続

API プロキシの作成時に「Add CORS」ポリシーを接続することで、CORS サポートを API プロキシに追加できます。このポリシーを追加するには、[Build a Proxy] ウィザードの [Security] ページで [Add CORS headers] チェックボックスを選択します。

このチェックボックスをオンにすると、次の図に示すように、Add CORS というポリシーが自動的にシステムに追加され、TargetEndpoint レスポンス プリフローに接続されます。

[Policies] の下のナビゲーターに追加された Add CORS ポリシーが、右側のペインで TargetEndpoint レスポンス プレフローに接続されている

Add CORS ポリシーは AssignMessage ポリシーとして実装されます。このポリシーが、適切なヘッダーをレスポンスに追加します。Add CORS ポリシーのための XML を以下に示します。基本的に、これらのヘッダーにより、リソースを共有するオリジンや許可するメソッドなどをブラウザに知らせることができます。CORS ヘッダーの詳細については、クロスオリジン リソース シェアリングの W3C 勧告をご覧ください。

次に示すように、content-type ヘッダーと authorization ヘッダー(Basic 認証または OAuth2 をサポートするために必要)を含めるようにポリシーを変更する必要があります。OAuth2 認証の場合は、RFC に準拠していない動作を修正する必要があります。

<AssignMessage async="false" continueOnError="false" enabled="true" name="add-cors">
    <DisplayName>Add CORS</DisplayName>
    <FaultRules/>
    <Properties/>
    <Set>
        <Headers>
            <Header name="Access-Control-Allow-Origin">{request.header.origin}</Header>
            <Header name="Access-Control-Allow-Headers">origin, x-requested-with, accept, content-type, authorization</Header>
            <Header name="Access-Control-Max-Age">3628800</Header>
            <Header name="Access-Control-Allow-Methods">GET, PUT, POST, DELETE</Header>
        </Headers>
    </Set>
    <IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
    <AssignTo createNew="false" transport="http" type="response"/>
</AssignMessage>

既存のプロキシへの CORS ヘッダーの追加

新しい Assign Message ポリシーを手動で作成し、前のセクションに示した Add CORS ポリシーのコードをそこにコピーする必要があります。その後、このポリシーを API プロキシの TargetEndpoint のレスポンス プリフローに接続します。ヘッダー値は必要に応じて変更できます。ポリシーの作成と接続の詳細については、ポリシーとはをご覧ください。

CORS プリフライト リクエストの処理

CORS プリフライトとは、サーバーにリクエストを送信して相手が CORS 対応かどうかを確認することを指します。一般的なプリフライト レスポンスには、サーバーが CORS リクエストを受け入れるオリジン、CORS リクエスト対応の HTTP メソッドの一覧、リソースリクエストの一部として使用できるヘッダー、プリフライトリクエストがキャッシュされる最長時間などが含まれます。サービスが CORS サポートを示していない場合や、クライアントのオリジンからのクロスオリジン リクエストを承諾しない場合は、ブラウザのクロスオリジン ポリシーが強制され、そのサーバーでホストされているリソースとやり取りするためにクライアントから行われたクロスドメイン リクエストは失敗します。

通常、CORS プリフライト リクエストは、HTTP OPTIONS メソッドによって行われます。CORS をサポートしているサーバーが OPTIONS リクエストを受信すると、CORS サポートのレベルを示す CORS ヘッダーのセットをクライアントに返します。この handshake の結果、クライアントは、オリジン以外のドメインからリクエストできる内容を理解します。

プリフライトの詳細については、クロスオリジン リソース シェアリングの W3C 勧告をご覧ください。CORS については、参考になるブログや記事が他にも多数あります。

Apigee には、すぐに使用できる CORS プリフライト ソリューションは用意されていませんが、このセクションの説明に従って実装できます。目標は、プロキシが条件付きフローで OPTIONS リクエストを評価するようにすることです。これにより、プロキシは、適切なレスポンスをクライアントに返すことができます。

まずサンプルフローを示し、その後、プリフライト リクエストを処理する各部分について説明します。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ProxyEndpoint name="default">
    <Description/>
    <Flows>
        <Flow name="OptionsPreFlight">
            <Request/>
            <Response>
                <Step>
                    <Name>add-cors</Name>
                </Step>
            </Response>
        <Condition>request.verb == "OPTIONS" AND request.header.origin != null AND request.header.Access-Control-Request-Method != null</Condition>
        </Flow>
    </Flows>

    <PreFlow name="PreFlow">
        <Request/>
        <Response/>

    </PreFlow>
    <HTTPProxyConnection>
        <BasePath>/v1/cnc</BasePath>
        <VirtualHost>default</VirtualHost>
        <VirtualHost>secure</VirtualHost>
    </HTTPProxyConnection>
    <RouteRule name="NoRoute">
        <Condition>request.verb == "OPTIONS" AND request.header.origin != null AND request.header.Access-Control-Request-Method != null</Condition>
    </RouteRule>
    <RouteRule name="default">
        <TargetEndpoint>default</TargetEndpoint>
   </RouteRule>
   <PostFlow name="PostFlow">
        <Request/>
        <Response/>
    </PostFlow>
</ProxyEndpoint>

この ProxyEndpoint では以下の部分が重要です。

  • RouteRule は、OPTIONS リクエストの条件を持つ NULL ターゲットに対して作成されています。TargetEndpoint が指定されていないことに注意してください。OPTIONS リクエストを受信し、Origin および Access-Control-Request-Method リクエスト ヘッダーが null でない場合、プロキシは直ちに、クライアントへのレスポンスで(実際のデフォルトの「バックエンド」ターゲットをバイパスして)CORS ヘッダーを返します。フロー条件と RouteRule の詳細については、フロー変数での条件をご覧ください。

    <RouteRule name="NoRoute">
        <Condition>request.verb == "OPTIONS" AND request.header.origin != null AND request.header.Access-Control-Request-Method != null</Condition>
    </RouteRule>
  • OPTIONS リクエストを受信し、Origin および Access-Control-Request-Method リクエスト ヘッダーが null でない場合、CORS ヘッダーを含めて Add CORS ポリシーをフローに追加する OptionsPreFlight フローが作成されます。

     <Flow name="OptionsPreFlight">
                <Request/>
                <Response>
                    <Step>
                        <Name>add-cors</Name>
                    </Step>
                </Response>
            <Condition>request.verb == "OPTIONS" AND request.header.origin != null AND request.header.Access-Control-Request-Method != null</Condition>
     </Flow>

サンプル CORS ソリューションの使用

共有フローとして実装されたサンプル CORS ソリューションは、GitHub で公開されています。共有フローバンドルを環境にインポートし、フローフックを使用して接続するか、API プロキシフローに直接接続します。詳細については、サンプルに付属の CORS-Shared-Flow README ファイルをご覧ください。