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

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

動画: API プロキシで CORS を有効にする方法について説明する短い動画をご覧ください。

一般的な CORS 使用例

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

    <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 は次のとおりです。

    <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</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 ヘッダーの詳細については、クロスオリジン リソース シェアリングの W3C 勧告をご覧ください。

既存のプロキシへの 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>