向 API 代理添加 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 代理,以便在后端调用服务 API。请记住,Apigee 位于客户端(本例中为浏览器)与后端 API(服务)之间。由于 API 代理在服务器上执行,而不是在浏览器中执行,因此等于能够成功调用服务。然后,只需将 CORS 标头附加到 TargetEndpoint 响应即可。只要浏览器支持 CORS,这些标头就会向浏览器表明可以放宽其同源政策,从而允许跨源 API 调用取得成功。

创建支持 CORS 的代理后,您可以在客户端代码中调用 API 代理网址,而不是后端服务。例如:

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

您可以通过以下方式向 API 代理附加添加 CORS 政策,从而为 API 代理添加 CORS 支持:

  • 创建政策时,在构建代理向导的安全页面中选中添加 CORS 标头复选框
  • 添加政策对话框中选择稍后添加

通过选中此复选框添加 CORS 政策后,名为添加 CORS (Add CORS) 的政策会自动添加到系统中,并附加到 TargetEndpoint 请求 PreFlow。

添加 CORS (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>

将 CORS 标头添加到现有代理

新版代理编辑器

如需将 CORS 政策添加到现有 API 代理,请执行以下操作:

  1. 如果您使用的是 Cloud 控制台中的 Apigee 界面:选择代理开发 > API 代理

    如果您使用的是经典版 Apigee 界面:请选择开发 > API 代理,然后在代理窗格中,选择代理的环境。

  2. 选择要向其添加 CORS 政策的 API 代理。
  3. 在新 API 代理的编辑器中,点击开发标签页。
  4. 在左侧窗格中,点击政策行中的 + 按钮。
  5. 创建政策对话框中,点击选择政策类型 (Select policy type) 字段,向下滚动到安全,然后选择 CORS

  6. 输入政策的详细信息,然后点击创建

  7. 在左侧窗格中,点击“目标端点”(Target Endpoints) > 默认下的 PreFlow
  8. 在左侧窗格中,依次选择“代理端点”(Proxy Endpoints) > 默认 > PreFlow

    在 Proxy Explorer 中选择的 PreFlow 的目标端点。

  9. 点击可视化编辑器右下角响应窗格中 PreFlow 旁边的 + 按钮:

    点击“响应”窗格中 PreFlow 旁边的 + 按钮。

  10. 添加政策步骤 (Add policy step) 对话框中,选择 CORS 政策。
  11. 点击添加以附加该政策。

经典版代理编辑器

如需将 CORS 政策添加到现有 API 代理,请执行以下操作:

  1. 登录 Apigee 界面
  2. 在左侧导航栏中,选择开发 > API 代理
  3. 如果您看到立即试用按钮,请点击该按钮以显示新版开发视图。

    开发视图如下所示。

    代理编辑器中的“开发”视图

  4. 选择要向其添加 CORS 政策的 API 代理。
  5. 在新 API 代理的编辑器中,点击开发标签页:
  6. 在左侧“导航器”窗格中,点击目标端点 > 默认下的 PreFlow
  7. 点击与请求 PreFlow 相对应的 +步骤按钮。此时会显示您可以创建的所有政策的分类列表。
  8. 安全类别中选择 CORS
  9. 指定名称(例如 Add CORS),然后点击添加

处理 CORS 预检请求

CORS 预检是指向服务器发送请求,以验证其是否支持 CORS。典型的预检响应包括服务器将从中接受 CORS 请求的域、CORS 请求支持的 HTTP 方法的列表、可以用作资源请求一部分的标头、将缓存预检响应的最长时间,等等。如果服务没有指明 CORS 支持,或者不希望接受来自客户端域的跨域请求,系统将强制执行浏览器的跨域政策,并且从客户端发出、与该服务器上托管的资源进行互动的任何跨网域请求都将失败。

CORS 预检请求通常使用 HTTP OPTIONS 方法发出。支持 CORS 的服务器收到 OPTIONS 请求后,它会向客户端返回一组 CORS 标头,以指示其 CORS 支持级别。由于存在此握手,客户端知道允许从非域网域请求的内容。

如需详细了解预检,请参阅跨域资源共享 W3C 建议。此外,您还可以参阅关于 CORS 的众多博客和文章。

Apigee 没有开箱即用的 CORS 预检解决方案,但可以按此部分所述实现。目标是让代理评估条件流中的 OPTIONS 请求。然后,代理可以将相应的响应发送回客户端。

我们来看一个示例流,然后讨论处理预检请求的各个部分:

<?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 的关键部分如下所示:

  • 系统会针对 NULL 目标创建 RouteRule,其中包含 OPTIONS 请求的条件。请注意,没有指定 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>
    
  • 系统将创建一个 OptionsPreFlight 流,以便在收到 OPTIONS 请求且 Origin 和 Access-Control-Control-Request-Method 请求标头不为 null 时,向该流添加“添加 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>