為 API Proxy 新增 CORS 支援

本頁適用於 ApigeeApigee Hybrid

查看 Apigee Edge 說明文件。

CORS (跨源資源共享) 是一種標準機制,可讓您在網頁中執行 JavaScript XMLHttpRequest (XHR) 呼叫,以便與非來源網域的資源互動。CORS 是所有瀏覽器強制執行的同源政策常見的實作解決方案。舉例來說,如果您透過在瀏覽器中執行的 JavaScript 程式碼,對 Twitter API 發出 XHR 呼叫,該呼叫將會失敗。這是因為向瀏覽器提供網頁的網域與提供 Twitter 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>

解決這個問題的方法之一,是建立 Apigee API Proxy,以便在後端呼叫服務 API。請注意,Apigee 會位於用戶端 (此處為瀏覽器) 和後端 API (服務) 之間。由於 API Proxy 是在伺服器上執行,而不是在瀏覽器中執行,因此可以成功呼叫服務。接著,您只需要將 CORS 標頭附加到 TargetEndpoint 回應即可。只要瀏覽器支援 CORS,這些標頭就會向瀏覽器發出信號,表示可以放寬同源政策,讓跨來源 API 呼叫成功。

建立支援 CORS 的 Proxy 後,您可以在用戶端程式碼中呼叫 API Proxy 網址,而非後端服務。例如:

<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>

將 CORS 政策附加至 ProxyEndpoint 的要求 PreFlow

將新增 CORS 政策附加至新的 API Proxy

您可以透過下列方式,將「Add CORS」政策附加至 API proxy,為 API proxy 新增 CORS 支援:

  • 在「建立 Proxy」精靈的「安全性」頁面中選取「新增 CORS 標頭」核取方塊,即可建立政策
  • 透過稍後在「新增政策」對話方塊中新增

當您勾選核取方塊新增 CORS 政策時,系統會自動新增名為「Add CORS」的政策,並附加至 TargetEndpoint 要求預檢。

「Add CORS」政策會在回應中加入適當的標頭。基本上,標頭可讓瀏覽器知道要與哪些來源共用資源,以及接受哪些方法等等。如要進一步瞭解這些 CORS 標頭,請參閱 跨源資源共享 W3C 建議

您應修改政策,如下所示:

  • content-typeauthorization 標頭 (必須支援基本驗證或 OAuth2) 新增至 Access-Control-Allow-Headers 標頭,如以下程式碼摘錄所示。
  • 針對 OAuth2 驗證,您可能需要採取措施修正 不符合 RFC 的行為
<CORS continueOnError="false" enabled="true" name="add-cors">
    <DisplayName>Add CORS</DisplayName>
    <AllowOrigins>{request.header.origin}</AllowOrigins>
    <AllowMethods>GET, PUT, POST, DELETE</AllowMethods>
    <AllowHeaders>origin, x-requested-with, accept, content-type, authorization</AllowHeaders>
    <ExposeHeaders>*</ExposeHeaders>
    <MaxAge>3628800</MaxAge>
    <AllowCredentials>false</AllowCredentials>
    <GeneratePreflightResponse>true</GeneratePreflightResponse>
    <IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
</CORS>

在現有 Proxy 中新增 CORS 標頭

全新 Proxy 編輯器

如要將 CORS 政策新增至現有的 API Proxy,請按照下列步驟操作:

  1. 如果您使用的是 Cloud 控制台中的 Apigee UI:請依序選取「Proxy development」>「API Proxies」

    如果您使用的是傳統 Apigee UI:請依序選取「Develop」>「API Proxies」,然後在「Proxies」窗格中選取 Proxy 的環境。

  2. 選取要新增 CORS 政策的 API 代理程式。
  3. 在新 API Proxy 的編輯器中,按一下「Develop」分頁。
  4. 在左側窗格中,按一下「政策」列中的「+」按鈕。
  5. 在「Create policy」對話方塊中,點選「Select policy type」欄位,然後向下捲動至「Security」,並選取「CORS」

  6. 輸入政策詳細資料,然後按一下「建立」

  7. 在左側面板中,依序點選「目標端點」>「預設」下方的「PreFlow」
  8. 按一下視覺編輯器右下方的「Request」窗格中「PreFlow」旁的「+」按鈕。
  9. 在「新增政策步驟」對話方塊中,選取「CORS」CORS政策。
  10. 按一下「新增」,附加政策。

傳統 Proxy 編輯器

如要將 CORS 政策新增至現有的 API Proxy,請按照下列步驟操作:

  1. 登入 Apigee UI
  2. 在左側導覽列中,依序選取「Develop」>「API Proxies」
  3. 如果畫面上顯示「立即試用」按鈕,請按一下該按鈕,即可顯示新的「開發」檢視畫面。

    以下是「Develop」檢視畫面。

    在 Proxy 編輯器中開發檢視畫面

  4. 選取要新增 CORS 政策的 API 代理程式。
  5. 在新 API Proxy 的編輯器中,按一下「Develop」分頁:
  6. 在左側導覽面板中,依序點選「目標端點」>「預設」下方的「PreFlow」
  7. 按一下頂端的「+Step」按鈕,對應到「Request PreFlow」。這會顯示可建立的所有政策分類清單。
  8. 在「安全性」類別中選取「CORS」
  9. 提供名稱 (例如 Add CORS),然後按一下「新增」

處理 CORS 預檢要求

CORS 預檢是指向伺服器傳送要求,以驗證伺服器是否支援 CORS。一般預檢回應包括伺服器會接受哪些 CORS 要求來源、CORS 要求支援的 HTTP 方法清單、可用於資源要求的標頭、預檢回應的快取時間上限,以及其他項目。如果服務未表明支援 CORS,或不想接受來自用戶端來源的跨來源要求,則瀏覽器的跨來源政策將會強制執行,且從用戶端發出的任何跨網域要求,如果要與該伺服器上代管的資源互動,都會失敗。

通常,CORS 預檢要求會透過 HTTP OPTIONS 方法提出。當支援 CORS 的伺服器收到 OPTIONS 要求時,會將一組 CORS 標頭傳回給用戶端,指出其 CORS 支援層級。這項握手後,用戶端就會知道可以從非來源網域要求哪些內容。

如要進一步瞭解預先檢查,請參閱 跨來源資源共享 W3C 建議。此外,還有許多關於 CORS 的網誌和文章可供參考。

Apigee 並未預先提供 CORS 預檢解決方案,但可按照本節所述進行實作。目標是讓 Proxy 在條件式流程中評估 OPTIONS 要求。然後 Proxy 可以將適當的回應傳回用戶端。

我們來看看範例流程,然後討論處理預先檢查要求的部分:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ProxyEndpoint name="default">
    <Description/>
    <Flows>
        <Flow name="OptionsPreFlight">
            <Request>
                <Step>
                    <Name>add-cors</Name>
                </Step>
            </Request>
            <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 要求設定條件。請注意,系統並未指定 TargetEndpoint。如果收到 OPTIONS 要求,且 Origin 和 Access-Control-Request-Method 要求標頭不為空值,代理程會立即在回應中傳回 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 要求標頭不為空值,就會建立 OptionsPreFlight 流程,並在流程中新增 Add CORS 政策,其中包含 CORS 標頭。
     <Flow name="OptionsPreFlight">
                <Request>
                    <Step>
                        <Name>add-cors</Name>
                    </Step>
                </Request>
                <Response/>
            <Condition>request.verb == "OPTIONS" AND request.header.origin != null AND request.header.Access-Control-Request-Method != null</Condition>
     </Flow>