向 API 代理添加 CORS 支持

您正在查看的是 Apigee Edge 文档。
转到 Apigee X 文档
信息

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>

此问题的一种解决方案是创建一个 Apigee API 代理,以便在后端调用服务 API。请记住,Edge 位于客户端(在本例中为浏览器)和后端 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 政策附加到新的 API 代理

您可以向 API 代理添加 CORS 支持,只需在创建 API 代理时附加“添加 CORS”政策即可。如需添加此政策,请在“构建代理”向导的“安全性”页面中选中添加 CORS 标头复选框。

选中此复选框后,会将名为 Add CORS 的政策自动添加到系统中,并附加到 TargetEndpoint 响应 PreFlow 中,如下图所示:

在“政策”下方添加已添加到导航器的 CORS 政策,并采用右平移方式附加到的 TargetEndpoint 响应 PreFlow

添加 CORS 政策作为一项 AssignmentMessage 政策实现,该政策会将适当的标头添加到响应。基本上,标头可以告知浏览器它将与哪些域共享资源、它接受的方法等。如需详细了解这些 CORS 标头,请参阅跨域资源共享 W3C 建议

您应按如下方式修改政策:

  • content-typeauthorization 标头(支持基本身份验证或 OAuth2 时需要)添加到 Access-Control-Allow-Headers 标头中,如以下代码段所示。
  • 对于 OAuth2 身份验证,您可能需要采取一些措施来纠正不符合 RFC 标准的行为
  • 建议您使用 <Set> 设置 CORS 标头,而不是 <Add>,如下面的代码段所示。使用 <Add> 时,如果 Access-Control-Allow-Origin 标头已存在,您会收到以下错误:

    The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed.

    有关详情,请参阅 CORS 错误:标头包含多个值 '*, *',但只允许包含一个值

<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 标头添加到现有代理

您需要手动创建新的“分配消息”政策,并将上一部分中列出的“添加 CORS”政策复制到其中。然后,将政策附加到 API 代理的 TargetEndpoint 的响应 PreFlow。您可以根据需要修改标头值。如需详细了解如何创建和附加政策,请参阅什么是政策?

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

  • 系统会针对 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-Request-Method 请求标头不为 null 时将添加 CORS 政策(包含 CORS 标头)添加到该流中。

     <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 解决方案

GitHub 上提供一个以共享流的形式实现的 CORS 解决方案。将共享流软件包导入您的环境,并使用流钩子或直接将其附加到 API 代理流。如需了解详情,请参阅示例随附的 CORS-Shared-FLow README 文件。