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
,可從也託管在 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 網頁正確呈現和運作
-
從您的瀏覽器開啟 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