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 要求與回應。
針對 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 限制
瀏覽器中指定的相同第三方 Cookie 限制也適用於 AMP 中具有憑證的 CORS 要求。這些限制取決於瀏覽器和平台,但在某些瀏覽器中,只有當使用者先前在第一方 (最上層) 視窗中造訪過來源時,來源才能設定 Cookie。換句話說,只有在使用者直接造訪過來源網站本身之後才能設定 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://<發布商網域>.cdn.ampproject.org
(例如,https://nytimes-com.cdn.ampproject.org
)
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
處理變更狀態的要求
在處理可能會變更系統狀態的要求 (例如,使用者訂閱或取消訂閱郵寄清單) 之前,請檢查以下項目
是否已設定 Origin
標頭:
-
如果來源與以下值之一不符,請停止並傳回錯誤回應
<發布商網域>.cdn.ampproject.org
- 發布商的來源 (也就是您的來源)
其中
*
代表萬用字元比對,而不是實際的星號 (*)。 -
否則,請處理要求。
是否未設定 Origin
標頭:
- 確認要求是否包含
AMP-Same-Origin: true
標頭。如果要求未包含此標頭,請停止並傳回錯誤回應。 - 否則,請處理要求。
範例逐步解說:處理 CORS 要求與回應
CORS 要求傳送到您的端點時,有兩種情境需要考量
- 來自同源的要求。
- 來自快取來源的要求 (來自 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 網頁正確呈現且運作正常
-
從您的瀏覽器中,開啟 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
- 網址:
-
開啟瀏覽器的開發人員工具,並確認沒有錯誤,且所有資源都已正確載入。
驗證您的伺服器回應標頭
您可以使用 curl
指令來驗證您的伺服器是否傳送正確的 HTTP 回應標頭。在 curl
指令中,提供要求網址以及您想要加入的任何自訂標頭。
語法:curl <要求網址> -H <自訂標頭> - I
測試來自同源的要求
在同源要求中,AMP 系統會加入自訂的 AMP-Same-Origin:true
標頭。
以下是我們的 curl 指令,用於測試從 https://amp.dev.org.tw
到 examples.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