AMP

AMP 中的 CORS

許多 AMP 元件和擴充功能利用遠端端點,方法是使用跨來源資源共用 (CORS) 要求。本文件說明在 AMP 中使用 CORS 的主要層面。如要瞭解 CORS 本身,請參閱 W3 CORS 規格

- [為何我自己的來源需要 CORS?](#why-do-i-need-cors-for-my-own-origin-) - [針對 CORS 要求使用 Cookie](#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 快取 (不同的網域) 提供的快取網頁。當您的使用者按一下按鈕來更新網頁上的價格時,快取的 AMP 網頁會向您的來源網域傳送要求以取得價格,這造成來源不符 (快取 -> 來源網域)。為了允許這類跨來源要求,您需要處理 CORS,否則要求會失敗。


好的,我應該怎麼做?

  1. 對於會擷取動態資料的 AMP 網頁,請務必測試這些網頁的快取版本;不要只在您自己的網域上測試。(請參閱下方的「在 AMP 中測試 CORS」一節)
  2. 請按照本文件中的說明,處理 CORS 要求與回應。

針對 CORS 要求使用 Cookie

大多數使用 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。換句話說,只有在使用者直接造訪過來源網站本身之後才能設定 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 快取網址格式的相關資訊,請參閱以下資源

2) 允許同源要求

對於缺少 Origin 標頭的同源要求,AMP 會設定以下自訂標頭

AMP-Same-Origin: true

當在同源 (亦即從非快取網址提供的文件) 上提出 XHR 要求時,AMP Runtime 會傳送這個自訂標頭。允許包含 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 和 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 範例程式碼

以下是一個範例 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-Same-Origin: true 的自訂 AMP 要求標頭。我們可以允許此要求,因為它來自同源 (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

  • 網址 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 網頁正確呈現且運作正常

  1. 從您的瀏覽器中,開啟 AMP 快取將用來存取您的 AMP 網頁的網址。您可以從 AMP By Example 上的這個工具判斷快取網址格式。

    例如

    • 網址:https://amp.dev.org.tw/documentation/guides-and-tutorials/start/create/
    • AMP 快取網址格式:https://www-ampproject-org.cdn.ampproject.org/c/s/www.ampproject.org/docs/tutorials/create.html
  2. 開啟瀏覽器的開發人員工具,並確認沒有錯誤,且所有資源都已正確載入。

驗證您的伺服器回應標頭

您可以使用 curl 指令來驗證您的伺服器是否傳送正確的 HTTP 回應標頭。在 curl 指令中,提供要求網址以及您想要加入的任何自訂標頭。

語法curl <要求網址> -H <自訂標頭> - I

測試來自同源的要求

在同源要求中,AMP 系統會加入自訂的 AMP-Same-Origin:true 標頭。

以下是我們的 curl 指令,用於測試從 https://amp.dev.org.twexamples.json 檔案 (在同一個網域上) 的要求

curl 'https://amp.dev.org.tw/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.dev.org.tw
access-control-allow-methods: POST, GET, OPTIONS

測試來自快取 AMP 網頁的要求

在非來自同網域的 CORS 要求 (亦即快取) 中,origin 標頭是要求的一部分。

以下是我們的 curl 指令,用於測試從 Google AMP 快取上快取的 AMP 網頁到 examples.json 檔案的要求

curl 'https://amp.dev.org.tw/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