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: <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。換句話說,只有在使用者直接造訪過來源網站本身之後才能設定。鑑於此,透過 CORS 存取的服務不能假設預設情況下能夠設定 Cookie。

AMP 中的 CORS 安全性

為了確保 AMP 網頁的要求和回應有效且安全,您必須

  1. 驗證要求.
  2. 傳送適當的回應標頭.

如果您在後端使用 Node,可以使用 AMP CORS 中介軟體,這是 AMP Toolbox 的一部分。

驗證 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: <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 要求標頭 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

  • 網址 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