AMP 中的 CORS
许多 AMP 组件和扩展通过使用跨源资源共享 (CORS) 请求来利用远程端点。本文档解释了在 AMP 中使用 CORS 的关键方面。要了解 CORS 本身,请参阅 W3 CORS 规范。
为什么我需要为自己的来源使用 CORS?
你可能会疑惑为什么需要 CORS 来请求你自己的来源,让我们来深入了解一下。
获取动态数据的 AMP 组件(例如 amp-form、amp-list 等)会向远程端点发出 CORS 请求来检索数据。如果你的 AMP 页面包含此类组件,则需要处理 CORS,以便这些请求不会失败。
让我们通过一个示例来说明这一点
假设你有一个 AMP 页面,其中列出了带有价格的产品。要更新页面上的价格,用户会单击一个按钮,该按钮会从 JSON 端点(通过 amp-list 组件完成)检索最新价格。JSON 在你的域名上。
好的,所以页面在我的域名上,JSON 在我的域名上。我看不出有什么问题!
啊,但你的用户是如何访问你的 AMP 页面的?他们访问的是缓存页面吗?很可能你的用户没有直接访问你的 AMP 页面,而是通过其他平台发现了你的页面。例如,Google 搜索使用 Google AMP 缓存来快速呈现 AMP 页面;这些是缓存页面,由 Google AMP 缓存提供,这是一个不同的域名。当你的用户单击按钮以更新页面上的价格时,缓存的 AMP 页面会向你的来源域名发送请求以获取价格,这是来源之间的不匹配(缓存 -> 来源域名)。要允许此类跨来源请求,你需要处理 CORS,否则请求会失败。
好的,我该怎么做?
- 对于获取动态数据的 AMP 页面,请确保测试这些页面的缓存版本;不要只在自己的域名上进行测试。(请参阅下面的在 AMP 中测试 CORS部分)
- 按照此文档中的说明处理 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>
通过指定凭据模式,源可以将 cookie 包含在 CORS 请求中,也可以在响应中设置 cookie(受第三方 cookie 限制的约束)。
第三方 cookie 限制
浏览器中指定的相同第三方 cookie 限制也适用于 AMP 中的凭据 CORS 请求。这些限制取决于浏览器和平台,但对于某些浏览器而言,只有当用户此前在第一方(顶级)窗口中访问过源时,源才能设置 cookie。或者换句话说,只有当用户直接访问源网站本身后。鉴于此,通过 CORS 访问的服务不能假设它默认能够设置 cookie。
AMP 中的 CORS 安全性
为确保 AMP 网页的请求和响应有效且安全,您必须
如果您在后端使用 Node,则可以使用AMP CORS 中间件,它是AMP 工具箱的一部分。
验证 CORS 请求
当您的端点收到 CORS 请求时
1) 允许针对特定 CORS 源的请求
CORS 端点通过 Origin
HTTP 头接收请求源。端点应仅允许来自以下位置的请求:(1) 发布商自己的源;以及 (2) https://cdn.ampproject.org/caches.json中列出的每个 cacheDomain
源。
例如,端点应允许来自以下位置的请求
- Google AMP 缓存子域:
https://<publisher's domain>.cdn.ampproject.org
(例如,https://nytimes-com.cdn.ampproject.org
)
2) 允许同源请求
对于缺少 Origin
头的同源请求,AMP 设置以下自定义头
AMP-Same-Origin: true
当在同源上发出 XHR 请求(即从非缓存 URL 提供的文档)时,AMP 运行时会发送此自定义头。允许包含 AMP-Same-Origin:true
头的请求。
发送 CORS 响应头
验证 CORS 请求后,生成的 HTTP 响应必须包含以下标头
Access-Control-Allow-Origin: <origin>
此标头是 W3 CORS 规范 的一项要求,其中 origin
指的是通过 CORS Origin
请求标头允许的请求来源(例如,"https://<publisher's subdomain>.cdn.ampproject.org"
)。
尽管 W3 CORS 规范允许在响应中返回 *
的值,但为了提高安全性,您应该
- 如果存在
Origin
标头,请验证并回显
标头值。Origin
处理状态更改请求
在处理可能更改系统状态的请求(例如,用户订阅或取消订阅邮件列表)之前,请检查以下内容
如果设置了 Origin
标头:
-
如果来源与以下值之一不匹配,请停止并返回错误响应
<publisher's domain>.cdn.ampproject.org
- 发布者的来源(即您的来源)
其中
*
表示通配符匹配,而不是实际星号 ( * )。 -
否则,处理请求。
如果未设置 Origin
标头:
- 验证请求是否包含
AMP-Same-Origin: true
标头。如果请求不包含此标头,请停止并返回错误响应。 - 否则,处理请求。
示例演练:处理 CORS 请求和响应
针对端点的 CORS 请求中有两种情况需要考虑
- 来自同一来源的请求。
- 来自缓存来源(来自 AMP 缓存)的请求。
让我们通过一个示例来了解这些情况。在我们的示例中,我们管理名为 article-amp.html.
的 AMP 页面,该页面托管在 example.com
网站上。AMP 页面包含一个 amp-list
,用于从 data.json
文件中检索动态数据,该文件也托管在 example.com
上。我们希望处理来自我们的 AMP 页面的对 data.json
文件的请求。这些请求可能来自同一来源(未缓存)上的 AMP 页面,也可能来自不同来源(已缓存)上的 AMP 页面。
允许的来源
根据我们对 CORS 和 AMP 的了解(来自上面的 验证 CORS 请求),对于我们的示例,我们将允许来自以下域的请求
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 示例代码
以下是一个我们可以用来处理 CORS 请求和响应的 JavaScript 函数示例
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 页面获取请求
在以下场景中,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 页面获取请求
在以下场景中,在 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
如下提供资源。
- 网址
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 页面缓存版本的测试。
通过缓存网址验证页面
要确保您的缓存 AMP 页面正确呈现和运行
-
从浏览器中,打开 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
- URL:
-
打开浏览器的开发工具,并验证没有错误,并且所有资源都已正确加载。
验证服务器响应标头
你可以使用 curl
命令来验证服务器是否发送了正确的 HTTP 响应标头。在 curl
命令中,提供请求 URL 和你希望添加的任何自定义标头。
语法:curl <request-url> -H <custom-header> - I
测试来自相同来源的请求
在相同来源请求中,AMP 系统会添加自定义 AMP-Same-Origin:true
标头。
以下是我们用于测试从 https://ampbyexample.com
到 examples.json
文件(在同一域名上)的请求的 curl 命令
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://ampbyexample.com access-control-allow-methods: POST, GET, OPTIONS
测试来自缓存的 AMP 页面的请求
在不是来自同一域(即缓存)的 CORS 请求中,origin
标头是请求的一部分。
以下是我们用于测试从 Google AMP 缓存上的缓存的 AMP 页面到 examples.json
文件的请求的 curl 命令
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