JavaScript 的同源策略及其"CORS"跨域方案
文章大綱
同源策略
同源是什麼?
在web瀏覽器中,同源策略 限制了從同一個源載入的文件或指令碼如何與來自另一個源的資源進行互動。這是一個用於隔離潛在惡意檔案的重要安全機制。
如果兩個頁面的
- 1.協議
- 2.埠(如果有指定)
- 3.域名
三者都相同,則兩個頁面具有相同的源。
舉例說明 http://store.example.com/dir/page.html
同源檢測的示例:
URL | 結果 | 原因 |
---|---|---|
http://store.example.com/index.html |
成功 | |
http://store.example.com/dir/another.html |
成功 | |
https://store.example.com/index.html |
失敗 | 不同協議 ( https和http ) |
http://store.example.com:81/index.html |
失敗 | 不同埠 ( 81和80) |
http://news.example.com/index.html |
失敗 | 不同域名 ( news和store ) |
http://example.com/index.html |
失敗 | 不同域名 (store是一個單獨的自域) |
如何跨源,以及場景應用
以下為4種可以遇到的跨源
源的更改
頁面可能會因某些限制而改變他的源。
設定 document.domain
的值,為其當前域或其當前域的父域。
場景
http://store.example.com/dir/page.html
文件中的一個指令碼執行以下語句document.domain = "company.com"
即可通過同源檢測
跨源網路訪問
同源策略控制了不同源之間的互動。
使用 CORS
允許跨源訪問。
場景 由瀏覽器發起的跨域 HTTP 請求 (這個大家接觸的最多)
跨源指令碼API訪問
Javascript的APIs中,允許文件間直接相互引用。但是當兩個文件的源不同時,一些引用方式將對 API物件的訪問新增限制
可以使用window.postMessage
場景 使用
<iframe>
巢狀的時候,父子頁面的通訊
跨源資料儲存訪問
儲存在瀏覽器中的資料,如localStorage和IndexedDB,以源進行分割。每個源都擁有自己單獨的儲存空間,一個源中的Javascript指令碼不能
對屬於其它源的資料進行讀寫操作
。
場景 null
瞭解CORS
CORS是什麼?
MDN的網站給出了這樣的2種解釋:
CORS (Cross-Origin Resource Sharing,跨域資源共享)是一個系統,它由一系列傳輸的HTTP頭組成,這些HTTP頭決定瀏覽器是否阻止前端 JavaScript 程式碼獲取跨域請求的響應。 CORS 給了web伺服器這樣的許可權,即伺服器可以選擇,允許跨域請求訪問到它們的資源。
跨域資源共享(CORS) 是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器 讓執行在一個 origin (domain) 上的Web應用被准許訪問來自不同源伺服器上的指定的資源。當一個資源從與該資源本身所在的伺服器不同的域、協議或埠請求一個資源時,資源會發起一個跨域 HTTP 請求。
實際上著兩種解釋都一樣。我給出這樣的理解:CORS賦予服務端(通常所說的後端)一個能力,自己控制哪些瀏覽器的請求可以訪問到它的資源,來解決跨域問題。
附:所有的 CORS
頭
HTTP頭 | 功能 |
---|---|
Access-Control-Allow-Origin |
指示請求的資源能共享給哪些域。 |
Access-Control-Allow-Credentials |
指示當請求的憑證標記為 true 時,是否響應該請求。 |
Access-Control-Allow-Headers |
用在對預請求的響應中,指示實際的請求中可以使用哪些 HTTP 頭。 |
Access-Control-Allow-Methods |
指定對預請求的響應中,哪些 HTTP 方法允許訪問請求的資源。 |
Access-Control-Expose-Headers |
指示哪些 HTTP 頭的名稱能在響應中列出。 |
Access-Control-Max-Age |
指示預請求的結果能被快取多久。 |
Access-Control-Request-Headers |
用於發起一個預請求,告知伺服器正式請求會使用那些 HTTP 頭。 |
Access-Control-Request-Method |
用於發起一個預請求,告知伺服器正式請求會使用哪一種 HTTP 請求方法。 |
Origin |
指示獲取資源的請求是從什麼域發起的。 |
CORS功能概述
功能概述 TL;DR
規範要求,對那些可能對伺服器資料產生副作用的 HTTP 請求方法(特別是 GET 以外的 HTTP 請求,或者搭配某些 MIME 型別的 POST 請求),瀏覽器必須首先使用 OPTIONS 方法發起一個預檢請求(preflight request),從而獲知服務端是否允許該跨域請求。伺服器確認允許之後,才發起實際的 HTTP 請求。在預檢請求的返回中,伺服器端也可以通知客戶端,是否需要攜帶身份憑證(包括 Cookies 和 HTTP 認證相關資料)。
一句話概述:非簡單請求時,會先傳送預檢請求,允許後再傳送實際請求
附:node-express框架下,服務端的跨域設定
app.all('*', function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Methods', '*');
next();
});
複製程式碼
CORS關於Cookie
CORS請求預設不傳送Cookie和HTTP認證資訊,如果想要知道用cookie就要注意3點
- Client端
new XMLHttpRequest()
中withCredentials
設定為true
- Serive端
HTTP頭
Access-Control-Allow-Credentials
設定為true
- Serive端
HTTP頭
Access-Control-Allow-Origin
不能設為星號,必須指定明確的、與請求網頁一致的域名
所以上面的例子要想傳送cookie
// service端
`res.header('Access-Control-Allow-Credentials', true);`
`res.header('Access-Control-Allow-Origin', '具體的域名');`
// client端
Jquery `ajax()` `xhrFields: {withCredentials: true}`
Axios `axios.defaults.withCredentials = true`
複製程式碼
CORS的簡單請求
上面講了簡單請求,但那些才是簡單請求呢?我們稱:若不會觸發 CORS 的預檢請求,稱這樣的請求為“簡單請求”
以下為簡單請求:
- HTTP Method 組成只能是以下幾種
- GET
- POST
- HEAD
- HTTP Headers 組成
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type 只包含以下型別 (form表單請求)
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
注:只有同時滿足以上兩個條件時,才是簡單請求,否則為非簡單請求
CORS預檢請求又是什麼?
前面說了這麼多預檢請求,我們來講一講什麼是預檢請求: 如果我們在client端傳送請求時,例如:
// 原生
var invocation = new XMLHttpRequest();
invocation.setRequestHeader('X-EXAMPLE', 'xixihaha');
invocation.setRequestHeader('Content-Type', 'application/xml');
// axios
axios.defaults.headers['X-EXAMPLE'] = 'xixihaha';
axios.defaults.headers['Content-Type'] = 'application/xml';
複製程式碼
POST 請求傳送一個 XML 文件,該請求包含了一個自定義的請求首部欄位(X-EXAMPLE: xixihaha)。另外,該請求的 Content-Type 為 application/xml。因此,該請求需要首先發起“預檢請求”。
server端 的HTTP頭設定
Access-Control-Allow-Origin: '具體的域名'
Access-Control-Allow-Methods: POST, GET, OPTIONS // 可包含的引數
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type // 允許的首部欄位
Access-Control-Max-Age: 86400
複製程式碼
非簡單請求和簡單請求無異,如果瀏覽器的預檢請求被伺服器接受,則傳送實際請求,未被接受則拒絕請求。
其他
跨域不止於此
JSONP
動態建立script標籤,然後利用script的src 不受同源策略約束來跨域獲取資料
function addScriptTag() {
var script = document.createElement("script");
script.src = "http://foo.example?callback=handleResponse";
document.body.appendChild(script);
}
function handleResponse() {
console.log('跨域資料');
};
複製程式碼
以下跨域方案不做過多解釋
上文提到的
postMessage()
nginx
轉發,即架設伺服器代理
window.name