1995年,同源政策由 Netscape 公司引入瀏覽器。目前,全部瀏覽器都實行這個政策。html
最初,它的含義是指,A 網頁設置的 Cookie,B 網頁不能打開,除非這兩個網頁「同源」。所謂「同源」指的是「三個相同」:前端
同源政策的目的,是爲了保證用戶信息的安全,防止惡意的網站竊取數據。java
設想這樣一種狀況:A 網站是一家銀行,用戶登陸之後,A 網站在用戶的機器上設置了一個 Cookie,包含了一些隱私信息(好比存款總額)。用戶離開 A 網站之後,又去訪問 B 網站,若是沒有同源限制,B 網站能夠讀取 A 網站的 Cookie,那麼隱私信息就會泄漏。更可怕的是,Cookie 每每用來保存用戶的登陸狀態,若是用戶沒有退出登陸,其餘網站就能夠冒充用戶,隨心所欲。由於瀏覽器同時還規定,提交表單不受同源政策的限制。webpack
因而可知,同源政策是必需的,不然 Cookie 能夠共享,互聯網就毫無安全可言了。web
隨着互聯網的發展,同源政策愈來愈嚴格。目前,若是非同源,共有三種行爲受到限制。json
瀏覽器的同源策略會致使跨域,也就是說,若是協議、域名或者端口有一個不一樣,都被看成是不一樣的域,就不能使用 Ajax 向不一樣源的服務器發送 HTTP 請求。首先咱們要明確一個問題,請求跨域了,請求到底發出去沒有?答案是確定發出去了,可是瀏覽器攔截了響應。segmentfault
Ajax 的同源策略主要是爲了防止 CSRF
(跨站請求僞造) 攻擊,若是沒有 AJAX 同源策略,至關危險,咱們發起的每一次 HTTP 請求都會帶上請求地址對應的 cookie,那麼能夠作以下攻擊:後端
DOM同源策略也同樣,若是 iframe
之間能夠跨域訪問,能夠這樣攻擊:api
因此說有了跨域跨域限制以後,咱們才能更安全的上網了。跨域
CORS 是一個 W3C 標準,全稱是跨域資源共享(Cross-origin resource sharing),它容許瀏覽器向跨源服務器,發出XMLHttpRequest請求。
整個 CORS 通訊過程,都是瀏覽器自動完成,不須要用戶參與。對於開發者來講,CORS 通訊與普通的 AJAX 通訊沒有差異,代碼徹底同樣。瀏覽器一旦發現 AJAX 請求跨域,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感知。所以,實現 CORS 通訊的關鍵是服務器。只要服務器實現了 CORS 接口,就能夠跨域通訊。
CORS經常使用的配置項有如下幾個:
Access-Control-Allow-Origin(必含) – 容許的域名,只能填 *
(通配符)或者單域名。
Access-Control-Allow-Methods(必含) – 這容許跨域請求的 http 方法(常見有 POST、GET、OPTIONS
)。
Access-Control-Allow-Headers(當預請求中包含 Access-Control-Request-Headers
時必須包含) – 這是對預請求當中 Access-Control-Request-Headers
的回覆,和上面同樣是以逗號分隔的列表,能夠返回全部支持的頭部。
Access-Control-Allow-Credentials(可選) – 表示是否容許發送Cookie,只有一個可選值:true(必爲小寫)。若是不包含cookies,請略去該項,而不是填寫false。這一項與 XmlHttpRequest 對象當中的 withCredentials
屬性應保持一致,即 withCredentials 爲true時該項也爲true;withCredentials 爲false時,省略該項不寫。反之則致使請求失敗。
Access-Control-Max-Age(可選) – 以秒爲單位的緩存時間。在有效時間內,瀏覽器無須爲同一請求再次發起預檢請求。
瀏覽器先根據同源策略對前端頁面和後臺交互地址作匹配,若同源,則直接發送數據請求;若不一樣源,則發送跨域請求。
服務器收到瀏覽器跨域請求後,根據自身配置返回對應文件頭。若未配置過任何容許跨域,則文件頭裏不包含 Access-Control-Allow-origin
字段,若配置過域名,則返回 Access-Control-Allow-origin + 對應配置規則裏的域名的方式
。
瀏覽器根據接受到的 響應頭裏的 Access-Control-Allow-origin
字段作匹配,若無該字段,說明不容許跨域,從而拋出一個錯誤;如有該字段,則對字段內容和當前域名作比對,若是同源,則說明能夠跨域,瀏覽器接受該響應;若不一樣源,則說明該域名不可跨域,瀏覽器不接受該響應,並拋出一個錯誤。
上面說到的兩種類型的報錯,控制檯輸出是不同的:
Access-Control-Allow-Origin
字段,就知道出錯了,從而拋出一個錯誤,被 XMLHttpRequest的onerror 回調函數捕獲。注意,這種錯誤沒法經過狀態碼識別,由於 HTTP 迴應的狀態碼有多是200。<!--控制檯返回結果-->
XMLHttpRequest cannot load http://localhost/city.json.
The 'Access-Control-Allow-Origin' header has a value 'http://segmentfault.com' that is not equal to the supplied origin.
Origin 'http://www.zhihu.com' is therefore notallowed access.
複製代碼
<!--控制檯返回結果-->
XMLHttpRequest cannot load http://localhost/city.json.
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'http://www.zhihu.com' is therefore not allowed access.
複製代碼
實際上瀏覽器將CORS請求分紅兩類:簡單請求(simple request
)和非簡單請求(not-so-simple request
)。
簡單請求是指知足如下條件的(通常只考慮前面兩個條件便可):
GET、POST、HEAD
其中一種請求方法。application/x-www-form-urlencoded、multipart/form-data、text/plain
對於簡單請求,瀏覽器直接發起 CORS 請求,具體來講就是服務器端會根據請求頭信息中的 origin
字段(包括了協議 + 域名 + 端口),來決定是否贊成此次請求。
若是 origin
指定的源在許可範圍內,服務器返回的響應,會多出幾個頭信息字段:
Access-Control-Allow-Origin: http://xxx.xxx.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
複製代碼
非簡單請求時指那些對服務器有特殊要求的請求,好比請求方法是 put
或 delete
,或者 content-type
的類型是 application/json
。其實簡單請求以外的都是非簡單請求了。
非簡單請求的 CORS 請求,會在正式通訊以前,使用 OPTIONS
方法發起一個預檢(preflight)請求到服務器,瀏覽器先詢問服務器,當前網頁所在的域名是否在服務器的許可名單之中,以及可使用哪些 HTTP 動詞和頭信息字段。只有獲得確定答覆,瀏覽器纔會發出正式的 XMLHttpRequest 請求,不然就報錯。
下面是一個預檢請求的頭部:
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
複製代碼
一旦服務器經過了"預檢"請求,之後每次瀏覽器正常的CORS請求,就都跟簡單請求同樣了。
關於爲何要有簡單請求和非簡單請求,可參考知乎上的一個回答 爲何跨域的post請求區分爲簡單請求和非簡單請求和content-type相關?
JSONP 的原理就是利用 <script>
標籤的 src 屬性沒有跨域的限制,經過指向一個須要訪問的地址,由服務端返回一個預先定義好的 Javascript 函數的調用,而且將服務器數據以該函數參數的形式傳遞過來,此方法須要先後端配合完成。
//定義獲取數據的回調方法
function getData(data) {
console.log(data);
}
// 建立一個script標籤,而且告訴後端回調函數名叫 getData
var body = document.getElementsByTagName('body')[0];
var script = document.gerElement('script');
script.type = 'text/javasctipt';
script.src = 'demo.js?callback=getData';
body.appendChild(script);
//script 加載完畢以後從頁面中刪除,不然每次點擊生成許多script標籤
script.onload = function () {
document.body.removeChild(script);
}
複製代碼
JSONP 使用簡單且兼容性不錯,可是隻限於 get
請求。
瀏覽器有跨域限制,可是服務器不存在跨域問題,因此能夠由服務器請求所要域的資源再返回給客戶端。
通常咱們在本地環境開發時,就是使用 webpack-dev-server
在本地開啓一個服務進行代理訪問的。
該方式只能用於二級域名相同的狀況下,好比 a.test.com
和 b.test.com
適用於該方式。
只須要給兩個頁面都添加 document.domain = 'test.com'
,經過在 a.test.com
建立一個 iframe
,去控制 iframe
的 window
,從而進行交互。
window.postMessage 是一個 HTML5 的 api,容許兩個窗口之間進行跨域發送消息。
這種方式一般用於獲取嵌入頁面中的第三方頁面數據。一個頁面發送消息,另外一個頁面判斷來源並接收消息
// 發送消息端
var receiver = document.getElementById('receiver').contentWindow;
var btn = document.getElementById('send');
btn.addEventListener('click', function (e) {
e.preventDefault();
var val = document.getElementById('text').value;
receiver.postMessage("Hello "+val+"!", "http://res.42du.cn");
});
// 接收消息端
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event){
if (event.origin !== "http://www.42du.cn")
return;
}
複製代碼
詳情可參考 MDN | window.postMessage
還有一些方法,好比window.name和location.hash。都比較適用於 iframe 的跨域,不過 iframe 用的比較少了,因此這些方法也就有點過期了。