概念
跨源資源共享 (Cross-origin resource sharing,簡稱CORS) (或通俗地譯為跨域資源共享)是一種基於HTTP頭的機制,該機制通過允許服務器標示除了它自己以外的其它orgin(域,協議和端口),這樣瀏覽器可以訪問加載這些資源。跨源資源共享還通過一種機制來檢查服務器是否會允許要發送的真實請求,該機制通過瀏覽器發起一個到服務器托管的跨源資源的"預檢"請求。在預檢中,瀏覽器發送的頭中標示有HTTP方法和真實請求中會用到的頭。
支持
現代瀏覽器支持在 API 容器中(例如 XMLHttpRequest 或 Fetch )使用 CORS,以降低跨源 HTTP 請求所帶來的風險。
CORS需要瀏覽器和服務器同時支持
- 整個CORS通信過程,都是瀏覽器自動完成,不需要用戶參與(瀏覽器一旦發現請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感覺)
- 只要服務器實現了CORS接口,便可以實現跨源通信
目前,所有瀏覽器都支持該功能,IE瀏覽器不能低於IE10。
兩種請求
瀏覽器將CORS請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。
簡單請求
簡單請求不會觸發 CORS 預檢請求。請注意,該術語並不屬於 Fetch (其中定義了 CORS)規范。
條件
若請求滿足所有下述條件,則該請求可視為簡單請求:
- 使用下列方法之一:
- GET
- HEAD
- POST
- 除了被用戶代理自動設置的首部字段(例如 Connection ,User-Agent)和在 Fetch 規范中定義為禁用首部名稱 的其他首部,允許人為設置的字段為 Fetch 規范定義的 對 CORS 安全的首部字段集合。該集合為:
- Accept
- Accept-Language
- Content-Language
- Content-Type (需要注意額外的限制)
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
- Content-Type 的值僅限於下列三者之一:
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
- 請求中的任意XMLHttpRequestUpload 對象均沒有注冊任何事件監聽器;XMLHttpRequestUpload 對象可以使用 XMLHttpRequest.upload 屬性訪問。
- 請求中沒有使用 ReadableStream 對象。
特點
對於簡單請求,瀏覽器直接發出CORS請求。具體來說,就是在頭信息之中,增加一個Origin字段。
實例
// 客戶端:http://127.0.0.1:3000
fetch("http://127.0.0.1:4000/simple", {
method: 'GET',
headers: {
'Content-Type': 'text/plain',
},
}).then(result => {
return result.text();
}).then(res => {
console.log('getRequest is succeed:', res);
}).catch(err => {
console.error('getRequest is faild:', err)
})
// 服務端:http://127.0.0.1:3000
const http = require("http");
http.createServer((request,response)=>{
if(pathname==='/simple'){
response.writeHead(200, {
"Content-Type": "text/plain",
"Access-Control-Allow-Origin":"http://127.0.0.1:3000"
})
response.write("simple request is succeed!");
}
response.end();
}).listen(4000);
為了清楚的看到簡單請求和非跨域請求的區別,分別在http://127.0.0.1:3000
下以GET方法調用服務地址http://127.0.0.1:3000/simple
、http://127.0.0.1:4000/simple
- 調用
http://127.0.0.1:3000/simple
(非跨域請求)
- 調用
http://127.0.0.1:4000/simple
(簡單請求)
對比兩張圖可以發現,簡單請求的響應頭部多了Origin:http//127.0.0.1:3000
非簡單請求
條件
不同時滿足簡單請求條件的,即屬於非簡單請求。非簡單請求會觸發CORS預檢請求。
預檢請求
與前述簡單請求不同,“需預檢的請求”要求必須首先使用 OPTIONS
方法發起一個預檢請求到服務器,以獲知服務器是否允許該實際請求。"預檢請求“的使用,可以避免跨域請求對服務器的用戶數據產生未預期的影響。
實例
// 客戶端:http://127.0.0.1:3000
fetch("http://127.0.0.1:4000/no-simple", {
method: 'POST',
body: JSON.stringify({
name: 'zzcyes',
birth: '1997'
}),
headers: {
'Content-Type': 'application/json',
},
credentials: "include"
}).then(result => {
return result.text();
}).then(res => {
console.log('cookie:', document.cookie);
console.log('getRequest is succeed:', res);
}).catch(err => {
console.error('getRequest is faild:', err)
})
// 服務端:http://127.0.0.1:4000
// 服務端:http://127.0.0.1:3000
const http = require("http");
http.createServer((request,response)=>{
if(pathname==='/no-simple'){
response.writeHead(200, {
"Content-Type": "text/plain",
"Access-Control-Allow-Origin":"http://127.0.0.1:3000",
"Access-Control-Allow-Headers":"Content-Type,My-Cookie",
"Access-Control-Allow-Methods":"GET,POST,OPTIONS",
"Set-Cookie":"name=zzcyes",
"Access-Control-Allow-Credentials":true,
"Access-Control-Max-Age":"10",
})
response.write("no simple CORS request is succeed!");
}
response.end();
}).listen(4000);
為了清楚的看到簡單請求和非跨域請求的區別,分別在http://127.0.0.1:3000
下以POST方法調用服務地址http://127.0.0.1:3000/no-simple
、http://127.0.0.1:4000/no-simple
- 調用
http://127.0.0.1:3000/no-simple
(非跨域請求)
- 調用
http://127.0.0.1:4000/no-simple
(非簡單請求)
預檢請求
POST請求(這里請求與響應頭部均與非跨域的POST別無二致)
響應首部字段
字段 | 說明 |
---|---|
Access-Control-Allow-Origin | 該字段是必須的。它的值要么是請求時Origin 字段的值,要么是一個* ,表示接受任意域名的請求。 |
Access-Control-Allow-Methods | 該字段必需,它的值是逗號分隔的一個字符串,表明服務器支持的所有跨域請求的方法。注意,返回的是所有支持的方法,而不單是瀏覽器請求的那個方法。這是為了避免多次"預檢"請求。 |
Access-Control-Expose-Headers | 在跨源訪問時,XMLHttpRequest對象的getResponseHeader()方法只能拿到一些最基本的響應頭,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果要訪問其他頭,則需要服務器設置本響應頭。 |
Access-Control-Allow-Headers | 如果瀏覽器請求包括Access-Control-Request-Headers 字段,則Access-Control-Allow-Headers 字段是必需的。它也是一個逗號分隔的字符串,表明服務器支持的所有頭信息字段,不限於瀏覽器在"預檢"中請求的字段。 |
Access-Control-Allow-Credentials | Access-Control-Allow-Credentials 頭指定了當瀏覽器的credentials 設置為true時是否允許瀏覽器讀取response的內容。當用在對preflight預檢測請求的響應中時,它指定了實際的請求是否可以使用credentials 。請注意:簡單 GET 請求不會被預檢;如果對此類請求的響應中不包含該字段,這個響應將被忽略掉,並且瀏覽器也不會將相應內容返回給網頁。 |
Access-Control-Max-Age | 用來指定本次預檢請求的有效期(秒) |
請求首部字段
字段 | 說明 |
---|---|
Origin | Origin 首部字段表明預檢請求或實際請求的源站。 |
Access-Control-Request-Method | Access-Control-Request-Method 首部字段用於預檢請求。其作用是,將實際請求所使用的 HTTP 方法告訴服務器。 |
Access-Control-Request-Headers | Access-Control-Request-Headers 首部字段用於預檢請求。其作用是,將實際請求所攜帶的首部字段告訴服務器。 |
附帶身份憑證的請求
一般而言,對於跨源 XMLHttpRequest
或 Fetch 請求,瀏覽器不會發送身份憑證信息。如果要發送憑證信息,需要設置 XMLHttpRequest
的某個特殊標志位。