W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
如果我們向另一個網(wǎng)站發(fā)送 ?fetch
? 請求,則該請求可能會失敗。
例如,讓我們嘗試向 http://example.com
發(fā)送 fetch
請求:
try {
await fetch('http://example.com');
} catch(err) {
alert(err); // fetch 失敗
}
正如所料,獲取失敗。
這里的核心概念是 源(origin)—— 域(domain)/端口(port)/協(xié)議(protocol)的組合。
跨源請求 —— 那些發(fā)送到其他域(即使是子域)、協(xié)議或端口的請求 —— 需要來自遠(yuǎn)程端的特殊 header。
這個策略被稱為 “CORS”:跨源資源共享(Cross-Origin Resource Sharing)。
CORS 的存在是為了保護(hù)互聯(lián)網(wǎng)免受黑客攻擊。
說真的,在這說點兒題外話,講講它的歷史。
多年來,來自一個網(wǎng)站的腳本無法訪問另一個網(wǎng)站的內(nèi)容。
這個簡單有力的規(guī)則是互聯(lián)網(wǎng)安全的基礎(chǔ)。例如,來自 hacker.com
的腳本無法訪問 gmail.com
上的用戶郵箱?;谶@樣的規(guī)則,人們感到很安全。
在那時候,JavaScript 并沒有任何特殊的執(zhí)行網(wǎng)絡(luò)請求的方法。它只是一種用來裝飾網(wǎng)頁的玩具語言而已。
但是 Web 開發(fā)人員需要更多功能。人們發(fā)明了各種各樣的技巧去突破該限制,并向其他網(wǎng)站發(fā)出請求。
其中一種和其他服務(wù)器通信的方法是在那里提交一個 <form>
。人們將它提交到 <iframe>
,只是為了停留在當(dāng)前頁面,像這樣:
<!-- 表單目標(biāo) -->
<iframe name="iframe"></iframe>
<!-- 表單可以由 JavaScript 動態(tài)生成并提交 -->
<form target="iframe" method="POST" action="http://another.com/…">
...
</form>
因此,即使沒有網(wǎng)絡(luò)方法,也可以向其他網(wǎng)站發(fā)出 GET/POST 請求,因為表單可以將數(shù)據(jù)發(fā)送到任何地方。但是由于禁止從其他網(wǎng)站訪問 <iframe>
中的內(nèi)容,因此就無法讀取響應(yīng)。
確切地說,實際上有一些技巧能夠解決這個問題,這在 iframe 和頁面中都需要添加特殊腳本。因此,與 iframe 的通信在技術(shù)上是可能的?,F(xiàn)在我們沒必要講其細(xì)節(jié)內(nèi)容,我們還是讓這些古董代碼不要再出現(xiàn)了吧。
另一個技巧是使用 script
標(biāo)簽。script
可以具有任何域的 src
,例如 <script src="http://another.com/…" rel="external nofollow" >
。也可以執(zhí)行來自任何網(wǎng)站的 script
。
如果一個網(wǎng)站,例如 another.com
試圖公開這種訪問方式的數(shù)據(jù),則會使用所謂的 “JSONP (JSON with padding)” 協(xié)議。
這是它的工作方式。
假設(shè)在我們的網(wǎng)站,需要以這種方式從 http://another.com
網(wǎng)站獲取數(shù)據(jù),例如天氣:
gotWeather
?。// 1. 聲明處理天氣數(shù)據(jù)的函數(shù)
function gotWeather({ temperature, humidity }) {
alert(`temperature: ${temperature}, humidity: ${humidity}`);
}
src="http://another.com/weather.json?callback=gotWeather" rel="external nofollow"
的 <script>
標(biāo)簽,使用我們的函數(shù)名作為它的 callback
URL-參數(shù)。let script = document.createElement('script');
script.src = `http://another.com/weather.json?callback=gotWeather`;
document.body.append(script);
another.com
動態(tài)生成一個腳本,該腳本調(diào)用 gotWeather(...)
,發(fā)送它想讓我們接收的數(shù)據(jù)。// 我們期望來自服務(wù)器的回答看起來像這樣:
gotWeather({
temperature: 25,
humidity: 78
});
gotWeather
? 函數(shù)將運行,并且因為它是我們的函數(shù),我們就有了需要的數(shù)據(jù)。這是可行的,并且不違反安全規(guī)定,因為雙方都同意以這種方式傳遞數(shù)據(jù)。而且,既然雙方都同意這種行為,那這肯定不是黑客攻擊了?,F(xiàn)在仍然有提供這種訪問的服務(wù),因為即使是非常舊的瀏覽器它依然適用。
不久之后,網(wǎng)絡(luò)方法出現(xiàn)在了瀏覽器 JavaScript 中。
起初,跨源請求是被禁止的。但是,經(jīng)過長時間的討論,跨源請求被允許了,但是任何新功能都需要服務(wù)器明確允許,以特殊的 header 表述。
有兩種類型的跨源請求:
安全請求很簡單,所以我們先從它開始。
如果一個請求滿足下面這兩個條件,則該請求是安全的:
Accept
?,Accept-Language
?,Content-Language
?,Content-Type
? 的值為 ?application/x-www-form-urlencoded
?,?multipart/form-data
? 或 ?text/plain
?。任何其他請求都被認(rèn)為是“非安全”請求。例如,具有 PUT
方法或 API-Key
HTTP-header 的請求就不是安全請求。
本質(zhì)區(qū)別在于,可以使用 <form>
或 <script>
進(jìn)行安全請求,而無需任何其他特殊方法。
因此,即使是非常舊的服務(wù)器也能很好地接收安全請求。
與此相反,帶有非標(biāo)準(zhǔn) header 或者例如 DELETE
方法的請求,無法通過這種方式創(chuàng)建。在很長一段時間里,JavaScript 都不能進(jìn)行這樣的請求。所以,舊的服務(wù)器可能會認(rèn)為此類請求來自具有特權(quán)的來源(privileged source),“因為網(wǎng)頁無法發(fā)送它們”。
當(dāng)我們嘗試發(fā)送一個非安全請求時,瀏覽器會發(fā)送一個特殊的“預(yù)檢(preflight)”請求到服務(wù)器 —— 詢問服務(wù)器,你接受此類跨源請求嗎?
并且,除非服務(wù)器明確通過 header 進(jìn)行確認(rèn),否則非安全請求不會被發(fā)送。
現(xiàn)在,我們來詳細(xì)介紹它們。
如果一個請求是跨源的,瀏覽器始終會向其添加 Origin
header。
例如,如果我們從 https://javascript.info/page
請求 https://anywhere.com/request
,請求的 header 將如下所示:
GET /request
Host: anywhere.com
Origin: https://javascript.info
...
正如你所看到的,Origin
包含了確切的源(domain/protocol/port),沒有路徑(path)。
服務(wù)器可以檢查 Origin
,如果同意接受這樣的請求,就會在響應(yīng)中添加一個特殊的 header Access-Control-Allow-Origin
。該 header 包含了允許的源(在我們的示例中是 https://javascript.info
),或者一個星號 *
。然后響應(yīng)成功,否則報錯。
瀏覽器在這里扮演受被信任的中間人的角色:
Origin
?。Access-Control-Allow-Origin
?,如果存在,則允許 JavaScript 訪問響應(yīng),否則將失敗并報錯。
這是一個帶有服務(wù)器許可的響應(yīng)示例:
200 OK
Content-Type:text/html; charset=UTF-8
Access-Control-Allow-Origin: https://javascript.info
對于跨源請求,默認(rèn)情況下,JavaScript 只能訪問“安全的” response header:
Cache-Control
?Content-Language
?Content-Type
?Expires
?Last-Modified
?Pragma
?訪問任何其他 response header 都將導(dǎo)致 error。
請注意:
請注意:列表中沒有
Content-Length
header!
該 header 包含完整的響應(yīng)長度。因此,如果我們正在下載某些內(nèi)容,并希望跟蹤進(jìn)度百分比,則需要額外的權(quán)限才能訪問該 header(請見下文)。
要授予 JavaScript 對任何其他 response header 的訪問權(quán)限,服務(wù)器必須發(fā)送 Access-Control-Expose-Headers
header。它包含一個以逗號分隔的應(yīng)該被設(shè)置為可訪問的非安全 header 名稱列表。
例如:
200 OK
Content-Type:text/html; charset=UTF-8
Content-Length: 12345
API-Key: 2c9de507f2c54aa1
Access-Control-Allow-Origin: https://javascript.info
Access-Control-Expose-Headers: Content-Length,API-Key
有了這種 Access-Control-Expose-Headers
header,此腳本就被允許讀取響應(yīng)的 Content-Length
和 API-Key
header。
我們可以使用任何 HTTP 方法:不僅僅是 GET/POST
,也可以是 PATCH
,DELETE
及其他。
之前,沒有人能夠設(shè)想網(wǎng)頁能發(fā)出這樣的請求。因此,可能仍然存在有些 Web 服務(wù)將非標(biāo)準(zhǔn)方法視為一個信號:“這不是瀏覽器”。它們可以在檢查訪問權(quán)限時將其考慮在內(nèi)。
因此,為了避免誤解,任何“非安全”請求 —— 在過去無法完成的,瀏覽器不會立即發(fā)出此類請求。首先,它會先發(fā)送一個初步的、所謂的“預(yù)檢(preflight)”請求,來請求許可。
預(yù)檢請求使用 OPTIONS
方法,它沒有 body,但是有三個 header:
Access-Control-Request-Method header
? 帶有非安全請求的方法。Access-Control-Request-Headers header
? 提供一個以逗號分隔的非安全 HTTP-header 列表。如果服務(wù)器同意處理請求,那么它會進(jìn)行響應(yīng),此響應(yīng)的狀態(tài)碼應(yīng)該為 200,沒有 body,具有 header:
Access-Control-Allow-Origin
? 必須為 ?*
? 或進(jìn)行請求的源(例如 ?https://javascript.info
?)才能允許此請求。Access-Control-Allow-Methods
? 必須具有允許的方法。Access-Control-Allow-Headers
? 必須具有一個允許的 header 列表。Access-Control-Max-Age
? 可以指定緩存此權(quán)限的秒數(shù)。因此,瀏覽器不是必須為滿足給定權(quán)限的后續(xù)請求發(fā)送預(yù)檢。
讓我們在一個跨源 PATCH
請求的例子中一步一步地看它是如何工作的(此方法經(jīng)常被用于更新數(shù)據(jù)):
let response = await fetch('https://site.com/service.json', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'API-Key': 'secret'
}
});
這里有三個理由解釋為什么它不是一個安全請求(其實一個就夠了):
PATCH
?Content-Type
? 不是這三個中之一:?application/x-www-form-urlencoded
?,?multipart/form-data
?,?text/plain
?。API-Key
? header。在發(fā)送我們的請求前,瀏覽器會自己發(fā)送如下所示的預(yù)檢請求:
OPTIONS /service.json
Host: site.com
Origin: https://javascript.info
Access-Control-Request-Method: PATCH
Access-Control-Request-Headers: Content-Type,API-Key
OPTIONS
?。/service.json
?。Origin
? —— 來源。Access-Control-Request-Method
? —— 請求方法。Access-Control-Request-Headers
? —— 以逗號分隔的“非安全” header 列表。服務(wù)應(yīng)響應(yīng)狀態(tài) 200 和 header:
Access-Control-Allow-Origin: https://javascript.info
?Access-Control-Allow-Methods: PATCH
?Access-Control-Allow-Headers: Content-Type,API-Key
?。這將允許后續(xù)通信,否則會觸發(fā)錯誤。
如果服務(wù)器將來需要其他方法和 header,則可以通過將這些方法和 header 添加到列表中來預(yù)先允許它們。
例如,此響應(yīng)還允許 ?PUT
?、?DELETE
? 以及其他 header:
200 OK
Access-Control-Allow-Origin: https://javascript.info
Access-Control-Allow-Methods: PUT,PATCH,DELETE
Access-Control-Allow-Headers: API-Key,Content-Type,If-Modified-Since,Cache-Control
Access-Control-Max-Age: 86400
現(xiàn)在,瀏覽器可以看到 PATCH
在 Access-Control-Allow-Methods
中,Content-Type,API-Key
在列表 Access-Control-Allow-Headers
中,因此它將發(fā)送主請求。
如果 Access-Control-Max-Age
帶有一個表示秒的數(shù)字,則在給定的時間內(nèi),預(yù)檢權(quán)限會被緩存。上面的響應(yīng)將被緩存 86400 秒,也就是一天。在此時間范圍內(nèi),后續(xù)請求將不會觸發(fā)預(yù)檢。假設(shè)它們符合緩存的配額,則將直接發(fā)送它們。
預(yù)檢成功后,瀏覽器現(xiàn)在發(fā)出主請求。這里的過程與安全請求的過程相同。
主請求具有 Origin
header(因為它是跨源的):
PATCH /service.json
Host: site.com
Content-Type: application/json
API-Key: secret
Origin: https://javascript.info
服務(wù)器不應(yīng)該忘記在主響應(yīng)中添加 Access-Control-Allow-Origin
。成功的預(yù)檢并不能免除此要求:
Access-Control-Allow-Origin: https://javascript.info
然后,JavaScript 可以讀取主服務(wù)器響應(yīng)了。
請注意:
預(yù)檢請求發(fā)生在“幕后”,它對 JavaScript 不可見。
JavaScript 僅獲取對主請求的響應(yīng),如果沒有服務(wù)器許可,則獲得一個 error。
默認(rèn)情況下,由 JavaScript 代碼發(fā)起的跨源請求不會帶來任何憑據(jù)(cookies 或者 HTTP 認(rèn)證(HTTP authentication))。
這對于 HTTP 請求來說并不常見。通常,對 http://site.com
的請求附帶有該域的所有 cookie。但是由 JavaScript 方法發(fā)出的跨源請求是個例外。
例如,fetch('http://another.com')
不會發(fā)送任何 cookie,即使那些 (!) 屬于 another.com
域的 cookie。
為什么?
這是因為具有憑據(jù)的請求比沒有憑據(jù)的請求要強(qiáng)大得多。如果被允許,它會使用它們的憑據(jù)授予 JavaScript 代表用戶行為和訪問敏感信息的全部權(quán)力。
服務(wù)器真的這么信任這種腳本嗎?是的,它必須顯式地帶有允許請求的憑據(jù)和附加 header。
要在 fetch
中發(fā)送憑據(jù),我們需要添加 credentials: "include"
選項,像這樣:
fetch('http://another.com', {
credentials: "include"
});
現(xiàn)在,fetch
將把源自 another.com
的 cookie 和我們的請求發(fā)送到該網(wǎng)站。
如果服務(wù)器同意接受 帶有憑據(jù) 的請求,則除了 Access-Control-Allow-Origin
外,服務(wù)器還應(yīng)該在響應(yīng)中添加 header Access-Control-Allow-Credentials: true
。
例如:
200 OK
Access-Control-Allow-Origin: https://javascript.info
Access-Control-Allow-Credentials: true
請注意:對于具有憑據(jù)的請求,禁止 Access-Control-Allow-Origin
使用星號 *
。如上所示,它必須有一個確切的源。這是另一項安全措施,以確保服務(wù)器真的知道它信任的發(fā)出此請求的是誰。
從瀏覽器角度來看,有兩種跨源請求:“安全”請求和其他請求。
“安全”請求必須滿足以下條件:
Accept
?Accept-Language
?Content-Language
?Content-Type
? 的值為 ?application/x-www-form-urlencoded
?,?multipart/form-data
? 或 ?text/plain
?。安全請求和其他請求的本質(zhì)區(qū)別在于,自古以來就可以使用 <form>
或 <script>
標(biāo)簽來實現(xiàn)安全請求,而對于瀏覽器來說,非安全請求在很長一段時間都是不可能的。
所以,實際區(qū)別在于,安全請求會立即發(fā)送,并帶有 Origin
header,而對于其他請求,瀏覽器會發(fā)出初步的“預(yù)檢”請求,以請求許可。
對于安全請求:
Origin
? header。Access-Control-Allow-Origin
? 為 ?*
? 或與 ?Origin
? 的值相同Access-Control-Allow-Origin
? 值與 ?Origin
? 的相同Access-Control-Allow-Credentials
? 為 ?true
?此外,要授予 JavaScript 訪問除 Cache-Control
,Content-Language
,Content-Type
,Expires
,Last-Modified
或 Pragma
外的任何 response header 的權(quán)限,服務(wù)器應(yīng)該在 header Access-Control-Expose-Headers
中列出允許的那些
header。
對于非安全請求,會在請求之前發(fā)出初步“預(yù)檢”請求:
OPTIONS
? 請求發(fā)送到相同的 URL:Access-Control-Request-Method
? 有請求方法。Access-Control-Request-Headers
? 以逗號分隔的“非安全” header 列表。Access-Control-Allow-Methods
? 帶有允許的方法的列表,Access-Control-Allow-Headers
? 帶有允許的 header 的列表,Access-Control-Max-Age
? 帶有指定緩存權(quán)限的秒數(shù)。你可能知道有一個 HTTP-header Referer
,它通常包含發(fā)起網(wǎng)絡(luò)請求的頁面的 url。
例如,當(dāng)從 http://javascript.info/some/url
fetch http://google.com
時,header 看起來如下:
Accept: */*
Accept-Charset: utf-8
Accept-Encoding: gzip,deflate,sdch
Connection: keep-alive
Host: google.com
Origin: http://javascript.info
Referer: http://javascript.info/some/url
正如你所看到的,存在 Referer
和 Origin
。
問題是:
Origin
?,如果 ?Referer
? 甚至具有更多信息?Referer
? 或 ?Origin
? 可行嗎,還是說會出問題?我們需要 Origin
,是因為有時會沒有 Referer
。例如,當(dāng)我們從 HTTPS(從高安全性訪問低安全性)fetch
HTTP 頁面時,便沒有 Referer
。
內(nèi)容安全策略 可能會禁止發(fā)送 Referer
。
正如我們將看到的,fetch
也具有阻止發(fā)送 Referer
的選項,甚至允許修改它(在同一網(wǎng)站內(nèi))。
根據(jù)規(guī)范,Referer
是一個可選的 HTTP-header。
正是因為 Referer
不可靠,才發(fā)明了 Origin
。瀏覽器保證跨源請求的正確 Origin
。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: