AMP

AMP 中的 CORS

许多 AMP 组件和扩展程序通过使用跨域资源共享 (CORS) 请求来利用远程端点。本文档介绍了在 AMP 中使用 CORS 的关键方面。要了解 CORS 本身,请参阅 W3 CORS 规范

- [为什么我自己的源需要 CORS?](#why-do-i-need-cors-for-my-own-origin-) - [将 Cookie 用于 CORS 请求](#utilizing-cookies-for-cors-requests) - [AMP 中的 CORS 安全性](#cors-security-in-amp) - [验证 CORS 请求](#verify-cors-requests) - [1) 允许来自特定 CORS 源的请求](#1-allow-requests-for-specific-cors-origins) - [2) 允许同源请求](#2-allow-same-origin-requests) * [发送 CORS 响应头](#send-cors-response-headers) - [Access-Control-Allow-Origin:<源>](#access-control-allow-origin-origin) * [处理状态更改请求](#processing-state-changing-requests) - [示例演练:处理 CORS 请求和响应](#example-walkthrough-handing-cors-requests-and-responses) - [在 AMP 中测试 CORS](#testing-cors-in-amp)

为什么我自己的源需要 CORS?

您可能对为什么自己源的请求需要 CORS 感到困惑,让我们深入探讨一下。

获取动态数据的 AMP 组件(例如,amp-form、amp-list 等)会向远程端点发出 CORS 请求以检索数据。如果您的 AMP 页面包含此类组件,则您需要处理 CORS,以使这些请求不会失败。

让我们用一个例子来说明这一点

假设您有一个 AMP 页面,其中列出了带有价格的产品。要更新页面上的价格,用户单击一个按钮,该按钮从 JSON 端点检索最新价格(通过 amp-list 组件完成)。JSON 在您的域名上。

好的,所以该页面在我的域名上,并且 JSON 也在我的域名上。我看不出有什么问题!

啊,但是您的用户如何访问您的 AMP 页面?他们访问的是缓存的页面吗?您的用户很可能不是直接访问您的 AMP 页面,而是通过其他平台发现您的页面。例如,Google 搜索使用 Google AMP 缓存来快速渲染 AMP 页面;这些是从 Google AMP 缓存提供的缓存页面,而 Google AMP 缓存是不同的域名。当您的用户单击按钮以更新页面上的价格时,缓存的 AMP 页面会向您的源域名发送请求以获取价格,这会在源之间产生不匹配(缓存 -> 源域名)。为了允许此类跨域请求,您需要处理 CORS,否则请求将失败。


好的,我该怎么办?

  1. 对于获取动态数据的 AMP 页面,请确保测试这些页面的缓存版本;不要只在您自己的域名上进行测试。(请参阅下面的在 AMP 中测试 CORS部分)
  2. 按照本文档中的说明处理 CORS 请求和响应。

将 Cookie 用于 CORS 请求

大多数使用 CORS 请求的 AMP 组件都会自动设置凭据模式,或者允许作者选择启用它。例如,amp-list 组件从 CORS JSON 端点获取动态内容,并允许作者通过 credentials 属性设置凭据模式。

示例:通过 Cookie 在 amp-list 中包含个性化内容

<amp-list
  credentials="include"
  src="<%host%>/json/product.json?clientId=CLIENT_ID(myCookieId)"
>
  <template type="amp-mustache">
    Your personal offer: ${{price}}  </template>
</amp-list>

通过指定凭据模式,源可以在 CORS 请求中包含 Cookie,也可以在响应中设置 Cookie(受第三方 Cookie 限制的约束)。

浏览器中指定的相同第三方 Cookie 限制也适用于 AMP 中使用凭据的 CORS 请求。这些限制取决于浏览器和平台,但对于某些浏览器,仅当用户之前在第一方(顶部)窗口中访问过该源时,该源才能设置 Cookie。或者,换句话说,仅当用户直接访问过该源网站本身之后。鉴于此,通过 CORS 访问的服务不能假定它默认情况下将能够设置 Cookie。

AMP 中的 CORS 安全性

为了确保 AMP 页面的请求和响应有效且安全,您必须

  1. 验证请求.
  2. 发送适当的响应头.

如果您在后端使用 Node,则可以使用 AMP CORS 中间件,它是 AMP 工具箱的一部分。

验证 CORS 请求

当您的端点收到 CORS 请求时

  1. 验证 CORS Origin 标头是否是允许的源(发布商的源 + AMP 缓存).
  2. 如果没有 Origin 标头,请检查请求是否来自同一源(通过 AMP-Same-Origin.

1) 允许来自特定 CORS 源的请求

CORS 端点通过 Origin HTTP 标头接收请求源。端点应仅允许来自以下来源的请求:(1) 发布商自己的源;以及 (2) https://cdn.ampproject.org/caches.json 中列出的每个 cacheDomain 源。

例如,端点应允许来自以下来源的请求

  • Google AMP 缓存子域:https://<发布商的域名>.cdn.ampproject.org
    (例如,https://nytimes-com.cdn.ampproject.org

有关 AMP 缓存 URL 格式的信息,请参阅以下资源

2) 允许同源请求

对于缺少 Origin 标头的同源请求,AMP 会设置以下自定义标头

AMP-Same-Origin: true

当在同一源上发出 XHR 请求时(即,从非缓存 URL 提供的文档),AMP 运行时会发送此自定义标头。允许包含 AMP-Same-Origin:true 标头的请求。

发送 CORS 响应头

验证 CORS 请求后,生成的 HTTP 响应必须包含以下标头

Access-Control-Allow-Origin:<源>

此标头是 W3 CORS 规范的要求,其中 origin 是指通过 CORS Origin 请求标头允许的请求源(例如,"https://<发布商的子域>.cdn.ampproject.org")。

尽管 W3 CORS 规范允许在响应中返回 * 值,但为了提高安全性,您应该

  • 如果存在 Origin 标头,请验证并回显 Origin 标头的值。

处理状态更改请求

在处理请求之前执行这些验证检查。此验证有助于防止 CSRF 攻击,并避免处理不受信任的源请求。

在处理可能更改系统状态的请求之前(例如,用户订阅或取消订阅邮件列表),请检查以下内容

如果设置了 Origin 标头:

  1. 如果源与以下值之一不匹配,请停止并返回错误响应

    • <发布商的域名>.cdn.ampproject.org
    • 发布商的源(即您的源)

    其中 * 表示通配符匹配,而不是实际的星号 ( * )。

  2. 否则,处理请求。

如果未设置 Origin 标头:

  1. 请验证请求是否包含 AMP-Same-Origin: true 标头。如果请求不包含此标头,则停止并返回错误响应。
  2. 否则,处理请求。

示例演练:处理 CORS 请求和响应

在向您的端点发送 CORS 请求时,需要考虑两种情况

  1. 来自相同来源的请求。
  2. 来自缓存来源(来自 AMP 缓存)的请求。

让我们通过一个示例来演示这些情况。在我们的示例中,我们管理着 example.com 网站,该网站托管着一个名为 article-amp.html 的 AMP 页面。该 AMP 页面包含一个 amp-list,用于从同样托管在 example.com 上的 data.json 文件中检索动态数据。我们希望处理来自 AMP 页面的、针对我们的 data.json 文件的请求。这些请求可能来自相同来源的 AMP 页面(未缓存),也可能来自不同来源的 AMP 页面(已缓存)。


允许的来源

根据我们从上面的 验证 CORS 请求 中了解到的关于 CORS 和 AMP 的知识,在我们的示例中,我们将允许来自以下域的请求

  • example.com --- 发布商的域
  • example-com.cdn.ampproject.org --- Google AMP 缓存子域

允许的请求的响应标头

对于来自允许来源的请求,我们的响应将包含以下标头

Access-Control-Allow-Origin: <origin>

这些是我们可能在 CORS 响应中包含的其他响应标头

Access-Control-Allow-Credentials: true
Content-Type: application/json
Access-Control-Max-Age: <delta-seconds>
Cache-Control: private, no-cache

伪 CORS 逻辑

我们用于处理 CORS 请求和响应的逻辑可以简化为以下伪代码

IF CORS header present
   IF origin IN allowed-origins
      allow request & send response
   ELSE
      deny request
ELSE
   IF "AMP-Same-Origin: true"
      allow request & send response
   ELSE
      deny request

CORS 示例代码

这是一个示例 JavaScript 函数,我们可以用它来处理 CORS 请求和响应

function assertCors(req, res, opt_validMethods, opt_exposeHeaders) {
  var unauthorized = 'Unauthorized Request';
  var origin;
  var allowedOrigins = [
    'https://example.com',
    'https://example-com.cdn.ampproject.org',
    'https://cdn.ampproject.org',
  ];
  var allowedSourceOrigin = 'https://example.com'; //publisher's origin
  // If same origin
  if (req.headers['amp-same-origin'] == 'true') {
    origin = sourceOrigin;
    // If allowed CORS origin & allowed source origin
  } else if (
    allowedOrigins.indexOf(req.headers.origin) != -1 &&
    sourceOrigin == allowedSourceOrigin
  ) {
    origin = req.headers.origin;
  } else {
    res.statusCode = 403;
    res.end(JSON.stringify({message: unauthorized}));
    throw unauthorized;
  }

  res.setHeader('Access-Control-Allow-Credentials', 'true');
  res.setHeader('Access-Control-Allow-Origin', origin);
}

注意:有关可运行的代码示例,请参阅 amp-cors.js

场景 1:来自相同来源的 AMP 页面的 Get 请求

在以下场景中,article-amp.html 页面请求 data.json 文件;来源相同。


如果我们检查请求,我们会发现

Request URL: https://example.com/data.json
Request Method: GET
AMP-Same-Origin: true

由于此请求来自相同来源,因此没有 Origin 标头,但存在自定义 AMP 请求标头 AMP-Same-Origin: true。我们可以允许此请求,因为它来自相同来源 (https://example.com)。

我们的响应标头将是

Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://example.com

场景 2:来自缓存的 AMP 页面的 Get 请求

在以下场景中,缓存在 Google AMP 缓存上的 article-amp.html 页面请求 data.json 文件;来源不同。


如果我们检查此请求,我们会发现

Request URL: https://example.com/data.json
Request Method: GET
Origin: https://example-com.cdn.ampproject.org

由于此请求包含 Origin 标头,我们将验证它是否来自允许的来源。我们可以允许此请求,因为它来自允许的来源。

我们的响应标头将是

Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://example-com.cdn.ampproject.org

使用缓存的字体

Google AMP 缓存会缓存 AMP HTML 文档、图像和字体,以优化 AMP 页面的速度。在加快 AMP 页面速度的同时,我们还希望谨慎地保护缓存的资源。我们将更改 AMP 缓存响应其缓存资源(通常是字体)的方式,方法是遵守来源的 Access-Control-Allow-Origin 值。

过去的表现(2019 年 10 月之前)

当 AMP 页面从 @font-face src 属性加载 https://example.com/some/font.ttf 时,AMP 缓存将缓存字体文件,并按如下方式提供资源,并使用通配符 Access-Control-Allow-Origin

  • URL https://example-com.cdn.ampproject.org/r/s/example.com/some/font.tff
  • Access-Control-Allow-Origin: *

新的表现(2019 年 10 月及之后)

虽然当前的实现是允许的,但这可能会导致跨源站点意外使用字体。在此更改中,AMP 缓存将开始使用原始服务器响应的完全相同的 Access-Control-Allow-Origin 值进行响应。要从缓存的 AMP 文档中正确加载字体,您需要通过标头接受 AMP 缓存的来源。

一个示例实现将是

function assertFontCors(req, res, opt_validMethods, opt_exposeHeaders) {
  var unauthorized = 'Unauthorized Request';
  var allowedOrigins = [
    'https://example.com',
    'https://example-com.cdn.ampproject.org',
  ];
  // If allowed CORS origin
  if (allowedOrigins.indexOf(req.headers.origin) != -1) {
    res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
  } else {
    res.statusCode = 403;
    res.end(JSON.stringify({message: unauthorized}));
    throw unauthorized;
  }
}

例如,如果您想在 https://example.com/amp.html 中加载 /some/font.ttf,则原始服务器应使用如下的 Access-Control-Allow-Origin 标头进行响应。


如果您的字体文件可以从任何来源访问,则可以使用通配符 Access-Control-Allow-Origin 进行响应,AMP 缓存也会回显该值,这意味着它将使用 Access-Control-Allow-Origin: * 进行响应。如果您已拥有此设置,则无需进行任何更改。

我们计划在 2019 年 10 月中旬左右进行此更改,并希望每个使用自托管字体的 AMP 发布商检查是否受到影响。

推出计划

  • 2019-09-30:版本包含对应用此更改的域进行更精确的控制。此版本应在本周内推出。
  • 2019-10-07:将为手动测试启用测试域。
  • 2019-10-14:(但取决于测试进展):该功能将普遍推出。

请在此处关注相关 问题

在 AMP 中测试 CORS

在测试 AMP 页面时,请务必包含来自 AMP 页面缓存版本的测试。

通过缓存 URL 验证页面

要确保缓存的 AMP 页面能够正确渲染和运行

  1. 从您的浏览器中,打开 AMP 缓存将用于访问您的 AMP 页面的 URL。您可以从此 AMP By Example 上的工具确定缓存 URL 的格式。

    例如

    • URL: https://amp.org.cn/documentation/guides-and-tutorials/start/create/
    • AMP 缓存 URL 格式:https://www-ampproject-org.cdn.ampproject.org/c/s/www.ampproject.org/docs/tutorials/create.html
  2. 打开浏览器的开发者工具,并验证是否没有错误并且所有资源都已正确加载。

验证您的服务器响应标头

您可以使用 curl 命令验证您的服务器是否发送了正确的 HTTP 响应标头。在 curl 命令中,提供请求 URL 和您希望添加的任何自定义标头。

语法curl <请求-url> -H <自定义-标头> - I

测试来自相同来源的请求

在同源请求中,AMP 系统会添加自定义 AMP-Same-Origin:true 标头。

这是我们的 curl 命令,用于测试从 https://amp.org.cnexamples.json 文件(在同一域上)的请求

curl 'https://amp.org.cn/static/samples/json/examples.json' -H 'AMP-Same-Origin: true' -I

命令的结果显示了正确的响应标头(注意:额外的信息已被修剪)

HTTP/2 200
access-control-allow-headers: Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token
access-control-allow-credentials: true
access-control-allow-origin: https://amp.org.cn
access-control-allow-methods: POST, GET, OPTIONS

测试来自缓存的 AMP 页面的请求

在非来自同一域(即缓存)的 CORS 请求中,origin 标头是请求的一部分。

这是我们的 curl 命令,用于测试从 Google AMP 缓存上的缓存 AMP 页面到 examples.json 文件的请求

curl 'https://amp.org.cn/static/samples/json/examples.json' -H 'origin: https://ampbyexample-com.cdn.ampproject.org' -I

命令的结果显示了正确的响应标头

HTTP/2 200
access-control-allow-headers: Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token
access-control-allow-credentials: true
access-control-allow-origin: https://ampbyexample-com.cdn.ampproject.org
access-control-allow-methods: POST, GET, OPTIONS