了解簡單 HTTP 和 XHR 請求之間的差異。
Origin Web 應用程式 - https://github.com
當您透過輸入 Origin Web App 開啟網站時,您的瀏覽器會發出簡單的 HTTP 請求。 CORS 在這裡不適用。因為我們自願要求提供來自 Origin Web App 的內容。您的瀏覽器將/應該毫無問題地提供它。但是,當涉及 XHR 請求時,瀏覽器中 Origin Web App 載入的 JavaScript 可以向任何請求資源的伺服器(https://netflix.com)發出請求。現在,Origin Web App 歸 Github 所有,但 https://netflix.com 歸 Netflix 所有,伺服器可能可以提供任何服務。 Github 無法控制 Netflix 擁有的伺服器。這會帶來許多安全隱患,可能會將內容從一個網站(可能是金融網站)竊取到任何遠端伺服器。
幸運的是,CORS 透過一組給定的規則很好地解決了這個問題。
它是 W3C 定義的標準,允許客戶端(瀏覽器)和伺服器之間的跨網域請求共享資源,同時保持安全性。任何瀏覽器都將遵守這些標準,以防止從任何第三方伺服器載入資源。
標頭指示請求的來源。它與 CORS 請求以及 POST 請求一起發送。
句法:
Origin: <scheme> "://" <hostname> [":" <port> ]
範例:
Origin: https://netflix.com
Origin: http://netflix.com:443
Origin: http://localhost:1443
瀏覽器在發出預檢請求時使用此標頭來指示發出實際請求時將使用哪種請求方法。
句法:
Access-Control-Request-Method: <method>
<method>
可以是GET
、 POST
或DELETE
。
例子:
Access-Control-Request-Method: POST
該標頭再次在瀏覽器的飛行前請求中使用,以指示發出實際請求時要使用哪些請求標頭。要使用多個標頭,必須用逗號分隔。
句法:
Access-Control-Request-Headers: <header-name>
Access-Control-Request-Headers: <header-name>, <header-name>
例子:
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
重要提示:所有標頭都應該是 CORS 安全清單或自訂標頭(例如X-Custom-Header
。
Accept
Accept-Language
Content-Language
Content-Type
允許的值為application/x-www-form-urlencoded
、 multipart/form-data
和text/plain
。
以下標頭將在預檢請求中傳回。
該標頭指示請求的 Origin 是否被允許。您的瀏覽器將透過將請求的來源與其匹配來選擇請求成功/失敗。
句法:
Access-Control-Allow-Origin: *
對於沒有憑證的請求,可以將值*
指定為通配符。這告訴您的瀏覽器允許來自任何來源的請求。
Access-Control-Allow-Origin: <origin>
當您在回應標頭中僅收到一個來源時,這表示您的伺服器/網路應用程式基於請求的Origin
如果允許,它會使用相同的Origin
進行回應。您的伺服器也應該回應 Vary,以表明它根據請求標頭而變化。
例子:
Access-Control-Allow-Origin: https://github.com
如果此標頭中的值之一匹配,您的瀏覽器將發出實際請求。當傳回通配符*
時,表示允許任何方法。
句法:
Access-Control-Allow-Methods: <method>, <method>, ...
Access-Control-Allow-Methods: *
例子:
Access-Control-Allow-Methods: GET, POST
如果允許所有請求的標頭,您的瀏覽器將發出實際請求。
句法:
Access-Control-Allow-Headers: <header-name>, <header-name>
Access-Control-Allow-Headers: *
例子:
Access-Control-Allow-Headers: Accept, Content-Type
通配符*
告訴瀏覽器允許任何標頭。
告訴飛行前的結果可以緩存多久。
Access-Control-Max-Age: <delta-seconds>
最大數量可以快取結果的秒數。
每個瀏覽器都有最大限制,
Vary header 通常用於快取目的,以確定是否可以使用快取的回應,或者是否必須根據 header 值重新快取它。
在 CORS 中,假設伺服器根據請求的Origin
允許多個來源,它將在Access-Control-Allow-Origin
中傳回特定的 URL。
句法:
Vary: <header-name>
例子:
假設 github.com 允許 https://github.com 和 https://netflix.com 請求資源。考慮以下場景,
場景一:
捲曲 -X 選項 https://github.com/api/v1/gists/1
Origin: https://github.com
Access-Control-Request-Method: GET
Access-Control-Request-Headers: Content-Type
Access-Control-Allow-Origin: https://github.com
Vary: Origin
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 600
現在在這種情況下,瀏覽器將快取此飛行前請求結果 10 分鐘。
場景2:
捲曲 -X 選項 https://github.com/api/v1/gists/1
Origin: https://netflix.com
Access-Control-Request-Method: GET
Access-Control-Request-Headers: Content-Type
Access-Control-Allow-Origin: https://netflix.com
Vary: Origin
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 300
現在您可能會注意到Access-Control-Allow-Origin
包含 https://netflix.com,這是一個回應如何根據給定Origin
變化的範例。與第一種情況不同,此回應的 max-age 也僅快取 5 分鐘。
某些請求不會觸發 CORS 的飛行前請求。我們將這些稱為簡單請求。應符合以下條件:
允許的方法之一:
除了使用者代理程式(例如Connection
或User-Agent
)自動設定的標頭之外,唯一允許手動設定的標頭是 CORS 安全列表請求標頭和以下內容:
DPR
Downlink
Save-Data
Viewport-Width
Width
請求中使用的任何XMLHttpRequestUpload
物件上都沒有註冊任何事件偵聽器。
請求中未使用ReadableStream
物件。
例子:
要求:
GET /api/v1/public-data/ HTTP/1.1
Host: bar.com
User-Agent: curl/7.69.1
Accept: */*
Origin: https://foo.com
響應標頭:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
當請求在Access-Control-Allow-Origin
標頭中返回*
。這意味著可以從任何Host
發出請求。在這種情況下,您的瀏覽器將不會發出飛行前請求。
您的瀏覽器將發出飛行前請求,以確定實際請求是否可以安全地發送。
例子:
const xhr = new XMLHttpRequest ( ) ;
xhr . open ( 'POST' , 'https://bar.com/api/v1/post-here/' ) ;
xhr . setRequestHeader ( 'X-PINGOTHER' , 'pingpong' ) ;
xhr . setRequestHeader ( 'Content-Type' , 'application/json;charset=UTF-8' ) ;
xhr . onreadystatechange = handler ;
xhr . send ( JSON . stringify ( { "email" : "[email protected]" } ) ) ;
飛行前要求:
OPTIONS /api/v1/post-here/
Host: bar.com
User-Agent: curl/7.69.1
Accept: */*
Origin: https://foo.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Type: application/json
Access-Control-Allow-Origin: https://foo.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type, Accept
實際要求:
POST -d '{foo: bar}' /api/v1/post-here/
Host: bar.com
User-Agent: curl/7.69.1
Accept: */*
Origin: https://foo.com
X-PINGOTHER: pingpong
Content-Type: application/json;charset=UTF-8
HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Type: application/json
Access-Control-Allow-Origin: https://foo.com
當設定像X-PINGOTHER
這樣的非標準標頭時,您的瀏覽器將不知道發出請求是否安全。為了確保其安全,您的瀏覽器會使用包含X-PINGOTHER
和Content-Type
Access-Control-Request-Headers
發出 OPTIONS 請求。在驗證飛行前請求的回應標頭後,您的瀏覽器會發出實際請求。
一般來說,當您發出 XHR 請求時,cookie 不會隨請求一起傳遞。當需要傳遞 cookie 時,您必須在XMLHttpRequest
物件上設定一個標誌。
const xhr = new XMLHttpRequest ( ) ;
const url = 'http://bar.com/api/v1/credentialed-content/' ;
function callOtherDomain ( ) {
if ( invocation ) {
xhr . open ( 'GET' , url , true ) ;
xhr . withCredentials = true ;
xhr . onreadystatechange = handler ;
xhr . send ( ) ;
}
}
要求:
POST -d '{foo: bar}' /api/v1/post-here/
Host: bar.com
User-Agent: curl/7.69.1
Accept: */*
Origin: https://foo.com
X-PINGOTHER: pingpong
Content-Type: application/json;charset=UTF-8
Cookie: _session=NyV14oKXiS6HHopaf9nT
當發出XMLHttpRequest
請求並設定了withCredentials
標誌時,瀏覽器將在請求標頭中傳遞Cookie
。
回覆:
HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Type: application/json
Access-Control-Allow-Origin: https://foo.com
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: _session=AjBSqxj4T7bSySNTWeEm; expires=Wed, 31-05-2020 00:00:00 GMT
當您的瀏覽器注意到Access-Control-Allow-Credentials
設定為 true 時。它將尊重Set-Cookie
標頭並設定 cookie。
重要提示:不應在Access-Control-Allow-Origin
中設定「*」通配符,如「憑證請求和通配符」部分中所提及的。
當透過設定withCredentials
標誌發出憑證請求時,伺服器必須設定Access-Control-Expose-Headers
以讓瀏覽器知道可以存取哪些標頭。
在 Cors 出現之前的世界中,預設情況下,瀏覽器無法在 CORS 請求中存取回應標頭。因此它是明確的,以便瀏覽器將查找此標頭以讀取公開的標頭。透過這種方式,CORS 規範可以確保舊瀏覽器不會崩潰。
這是在飛行前請求中返回的。當您的瀏覽器看到此資訊時,它可以存取Set-Cookie
標頭。正如我們上面提到的,在正常的 XHR 請求中,瀏覽器不會在請求標頭中傳遞Cookie
,也不會讀取Set-Cookie
回應標頭。
句法:
Access-Control-Allow-Credentials: true
您可以在「帶有憑證的請求」部分找到範例。
當我們說憑證請求時,它意味著在 XHR 請求中傳遞的 cookie 或透過回應標頭Set-Cookie
設定的 cookie。當使用 withCredentials 標誌發出 XHR 請求時,您希望收到回應以及要設定的 cookie。但是,您不能期望Access-Control-Allow-Origin
為“*”,因為這表示任何網站都可以使用這些 cookie。因此,如果您的瀏覽器在Access-Control-Allow-Origin
回應標頭中看到“*”,則請求將失敗。
當預檢請求回應 301/302 時,某些瀏覽器目前可能不支援此功能。您可能會收到以下錯誤:
The request was redirected to 'https://example.com/foo', which is disallowed for cross-origin requests that require preflight
Request requires preflight, which is disallowed to follow cross-origin redirect
注意:有關解決方法,請檢查 Mozilla 的預檢請求和重定向文件。
當使用者啟用時,瀏覽器具有拒絕所有third-party
cookie 的設定。例如,如果要求來自https://foo.com
且伺服器位於https://bar.com
,則您的瀏覽器將不會設定https://bar.com
所傳送的 cookie。