跨資源共享(CORS)
跨源資源共享(CORS,或通俗地譯為跨域資源共享)是一種基于 HTTP 頭的機制,該機制通過允許服務器標示除了它自己以外的其他源(域、協(xié)議或端口),使得瀏覽器允許這些源訪問加載自己的資源??缭促Y源共享還通過一種機制來檢查服務器是否會允許要發(fā)送的真實請求,該機制通過瀏覽器發(fā)起一個到服務器托管的跨源資源的“預檢”請求。在預檢中,瀏覽器發(fā)送的頭中標示有 HTTP 方法和真實請求中會用到的頭。
例如:運行在 https://baidu.com 的js代碼使用 XMLHttpRequest 向 https://google.com 發(fā)起請求。
出于安全性,瀏覽器限制腳本內發(fā)起的跨源 HTTP 請求。這意味著使用這些 API 的 Web 應用程序只能從加載應用程序的同一個域請求 HTTP 資源,除非響應報文包含了正確 CORS 響應頭。

CORS 機制允許 Web 應用服務器進行跨源訪問控制,從而使跨源數(shù)據(jù)傳輸?shù)靡园踩M行。
CORS 規(guī)范要求
對可能對服務器數(shù)據(jù)產生副作用的 HTTP 請求方法(特別是 GET 以外的 HTTP 請求,或者搭配某些 MIME 類型的 POST 請求),瀏覽器必須首先使用 OPTIONS 方法發(fā)起一個預檢請求(preflight request),從而獲知服務端是否允許該跨源請求。
服務器確認允許之后,才發(fā)起實際的 HTTP 請求。
在預檢請求的返回中,服務器端也可以通知客戶端,是否需要攜帶身份憑證(例如 Cookie 和 HTTP 認證相關數(shù)據(jù))
跨域請求的分類
簡單請求
簡單請求不會觸發(fā) CORS 預檢請求。
如果請求滿足所有下述條件,則該請求可視為簡單請求:
- 請求方法是以下三種方法之一:
- HEAD
- GET
- POST
- 除了被用戶代理自動設置的標頭字段(例如 Connection、User-Agent 或其他在 Fetch 規(guī)范中定義為禁用標頭名稱的標頭),允許人為設置的字段為 Fetch 規(guī)范定義的對 CORS 安全的標頭字段集合。該集合為:
- Accept
- Accept-Language
- Content-Language
- Content-Type 所制定的請求頭僅限于下列三者
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
- Range (只允許簡單的范圍標頭值 如 bytes=256- 或 bytes=127-255)
- 如果請求是使用 XMLHttpRequest 對象發(fā)出的,在返回的 XMLHttpRequest.upload 對象屬性上沒有注冊任何事件監(jiān)聽器;也就是說,給定一個 XMLHttpRequest 實例 xhr,沒有調用 xhr.upload.addEventListener(),以監(jiān)聽該上傳請求。
- 請求中沒有使用 ReadableStream 對象
例如: 站點 https://foo.example 網頁應用想要訪問 https://bar.other 的資源
fetch('https://bar.other/test', {
method: 'GET',
})
.then(res => res.json())
.then(res => console.log(res));
此操作實行了客戶端和服務器之間的簡單交換,使用 CORS 標頭字段來處理權限:

服務端返回的 Access-Control-Allow-Origin
標頭的 Access-Control-Allow-Origin: *
值表明,該資源可以被任意外源訪問
const express = require('express');
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.get('/test', (req, res) => {
res.header('Access-Control-Allow-Origin', '*');
res.send({
code: 0,
data: 'hello world'
});
});
app.listen(3000, () => {
console.log('server is running on port 3000');
});
如果 https://bar.other 的資源持有者想限制他的資源只能通過 https://foo.example 來訪問,可以這樣做:Access-Control-Allow-Origin: https://foo.example
預檢請求
當不滿足簡單請求的條件時,瀏覽器會發(fā)送預檢請求“需預檢的請求”要求必須首先使用 OPTIONS 方法發(fā)起一個預檢請求到服務器,以獲知服務器是否允許該實際請求。
"預檢請求“ 的使用,可以避免跨域請求對服務器的用戶數(shù)據(jù)產生未預期的影響。
如下是一個需要執(zhí)行預檢請求的 HTTP 請求:
fetch('https://bar.other/test', {
method: 'POST',
headers: {
'X-PINGOTHER': 'pingpong',
'Content-Type': 'application/xml',
}
})
.then(res => res.json())
.then(res => console.log(res));
上面的代碼發(fā)送 POST 請求,該請求包含了一個非標準的 HTTP X-PINGOTHER 請求標頭。這樣的請求標頭并不是 HTTP/1.1 的一部分,但通常對于 web 應用很有用處。另外,該請求的 Content-Type 為 application/xml,且使用了自定義的請求標頭,所以該請求需要首先發(fā)起“預檢請求”。

下面是服務端和客戶端完整的信息交互。
首次交互是預檢請求/響應
瀏覽器根據(jù)上面的 JavaScript 代碼片斷所使用的請求參數(shù),向服務端發(fā)送一個預檢請求,該請求的請求頭如下:
OPTIONS /doc HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
標頭字段 Access-Control-Request-Method 告知服務器,實際請求將使用 POST 方法。
標頭字段 Access-Control-Request-Headers 告知服務器,實際請求將攜帶兩個自定義請求標頭字段:X-PINGOTHER 與 Content-Type。
服務器根據(jù)預檢請求的請求頭信息,決定是否允許該實際請求。
HTTP/1.1 204 No Content
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
對于預檢請求,不需要響應任何的消息體,只需要在響應頭中添加:
-
Access-Control-Allow-Origin
:和簡單請求一樣,表示允許的源 -
Access-Control-Allow-Methods
:表示允許的后續(xù)真實的請求方法 -
Access-Control-Allow-Headers
:表示允許改動的請求頭 -
Access-Control-Max-Age
:告訴瀏覽器,多少秒內,對于同樣的請求源、方法、頭,都不需要再發(fā)送預檢請求了
預檢請求完成之后,發(fā)送實際請求
附帶身份憑證的請求
XMLHttpRequest 或 Fetch,可以基于 HTTP cookies 和 HTTP 認證信息發(fā)送身份憑證。默認情況下,跨域請求并不會附帶cookie。以通過簡單的配置就可以實現(xiàn)附帶cookie。
// xhr
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
// fetch api
fetch(url, {
credentials: "include"
})
當一個請求需要附帶cookie時,無論它是簡單請求,還是預檢請求,都會在請求頭中添加cookie
字段。
服務器響應時,需要明確告知客戶端:服務器允許這樣的憑據(jù)。需要在響應頭中添加:Access-Control-Allow-Credentials: true
。
在響應附帶身份憑證的請求時:
- 服務器不能將 Access-Control-Allow-Origin 的值設為通配符“*”,而應將其設置為特定的域,如:Access-Control-Allow-Origin: https://example.com。
- 服務器不能將 Access-Control-Allow-Headers 的值設為通配符“*”,而應將其設置為標頭名稱的列表,如:Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
- 服務器不能將 Access-Control-Allow-Methods 的值設為通配符“*”,而應將其設置為特定請求方法名稱的列表,如:Access-Control-Allow-Methods: POST, GET
在跨源訪問時,瀏覽器只能拿到一些最基本的響應頭,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果要訪問其他頭,則需要服務器設置本響應頭。
Access-Control-Expose-Headers
頭讓服務器把允許瀏覽器訪問的頭放入白名單,例如:文章來源:http://www.zghlxwxcb.cn/news/detail-835777.html
Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header
這樣JS就能夠訪問指定的響應頭了。文章來源地址http://www.zghlxwxcb.cn/news/detail-835777.html
到了這里,關于HTTP 第六章 跨資源共享(CORS)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!