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。換句話說,只有在使用者直接造訪過來源網站本身之後。鑑於此,透過 CORS 存取的服務不能假設它預設能夠設定 Cookie。
AMP 中的 CORS 安全性
為了確保您的 AMP 網頁的要求和回應有效且安全,您必須
如果您在後端使用 Node,您可以使用 AMP CORS 中介軟體,它是 AMP Toolbox 的一部分。
驗證 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: <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
,以從 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 範例程式碼
以下是我們可以使用的範例 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