參考:https://blog.csdn.net/frontender_way/article/details/70568113 javascript
1、同源策略:php
同源策略就是瀏覽器爲了保證用戶信息的安全,防止惡意的網站竊取數據,禁止不一樣域之間的JS進行交互。html
對於瀏覽器而言只要域名、協議、端口其中一個不一樣就會引起同源策略,從而限制他們之間以下的交互行爲:前端
(1)Cookie、LocalStorage 和 IndexDB 沒法讀取。
(2)DOM 沒法得到。
(3)AJAX 請求不能發送。html5
2、什麼是跨域?java
協議、主機地址(或域名)、端口其中有一項不一樣,就認爲是不一樣域,這之間發生任何數據交互都是跨域。node
3、什麼是JS跨域?jquery
兩個不一樣的域a、bnginx
在a的應用的js腳本中調用了b的後端地址。 web
js跨域是指經過js在不一樣的域之間進行數據傳輸或通訊,好比用ajax向一個不一樣的域請求數據,或者經過js獲取頁面中不一樣域的框架中(iframe)的數據。
只要協議、域名、端口有任何一個不一樣,都被看成是不一樣的域。
4、有哪些跨域方法?
(1)JSONP
問題: 假設 a 網站請求 b 網站的一個 js,這個 js 中請求了 b 網站的內容算跨域嗎。
在js中,咱們直接用XMLHttpRequest請求不一樣域上的數據時,是不能夠的。
可是,在頁面上引入不一樣域上的js腳本文件倒是能夠的,script標籤裏的src屬性來完成的,jsonp正是利用這個特性來實現的。
好比,新建一個crossDomain.html頁面,它裏面的代碼須要利用ajax獲取一個不一樣域上的json數據,假設這個json數據地址是http://192.168.x.xxx/JSONP/jsonpTest.php那麼crossDomain.html中的代碼就能夠這樣:
<script type="text/javascript"> var text = document.querySelector('.text'); function dosomething(jsondata) { var str = ""; for (var i = 0; i < jsondata.length; i++) { str += jsondata[i]; } text.innerHTML = '我是JS經過JSONP跨域請求來的數據:'+'<span class="show">'+str+'</span>'; } </script> <script type="text/javascript" src="http://192.168.x.xxx/JSONP/jsonpTest.php?callback=dosomething"></script>
能夠看到在獲取數據的地址後面還有一個callback參數,按慣例是用這個參數名,可是你用其餘的也同樣。固然若是獲取數據的jsonp地址頁面不是你本身能控制的,就得按照提供數據的那一方的規定格式來操做了。
由於是當作一個js文件來引入的,因此http://192.168.x.xxx/JSONP/jsonpTest.php返回的必須是一個能執行的js文件,因此這個頁面的php代碼多是這樣的:
<?php $callback = $_GET['callback'];//獲得回掉函數名 $data = array('a','b','c'); //要返回的數據 echo $callback.'('.json_encode($data).')'; //輸出 ?>
而後在crossDomain.html中打印出返回的jsondata以下:
["a", "b", "c"]
能夠看到請求成功了,而後就能夠在crossDomain.html這個頁面裏處理這個數據了。
這樣jsonp的原理就很清楚了,經過script標籤引入一個js文件,這個js文件載入成功後會執行咱們在url參數中指定的函數,而且會把咱們須要的json數據做爲參數傳入。因此jsonp是須要服務器端的頁面進行相應的配合的。
固然能夠直接用一些已經封裝過的庫,這樣就不用每次去建立script標籤了。以下爲JQ的跨域API:
$.getJSON('http://192.168.x.xxx/JSONP/jsonpTest.php?callback=?',function(jsondata){ console.log(jsondata);//["a", "b", "c"] var str = ""; $.each(jsondata,function(i,index){ return str += index; }); $(".text1").html('我是JQ經過JSONP跨域請求來的數據:'+'<span class="show">'+str+'</span>'); });
jquery的getJSON方法會自動生成一個全局函數來替換callback=?中的問號,以後獲取到數據後又會自動銷燬,實際上就是起一個臨時代理函數的做用。$.getJSON方法會自動判斷是否跨域,不跨域的話,就調用普通的ajax方法;跨域的話,則會以異步加載js文件的形式來調用jsonp的回調函數。
var script = document.createElement('script'); script.type = 'text/javascript'; // 傳參並指定回調執行函數爲onBack script.src = 'http://www.domain-com:8080/login?user=admin&callback=onBack'; document.head.appendChild(script); // 回調執行函數 function onBack(res) { alert(JSON.stringify(res)); }
(2)經過修改document.domain來跨子域
上面的jsonp是來解決ajax跨域請求的,那麼若是是須要處理 Cookie 和 iframe 該怎麼辦呢?這時候就能夠經過修改document.domain來跨子域。兩個網頁一級域名相同,只是二級域名不一樣,瀏覽器容許經過設置document.domain共享 Cookie或者處理iframe。好比A網頁是http://w1.example.com/a.html,B網頁是http://w2.example.com/b.html,那麼只要設置相同的document.domain,兩個網頁就能夠共享Cookie。
document.domain = 'example.com'; //如今,A網頁經過腳本設置一個 Cookie。 document.cookie = "test1=hello"; //B網頁就能夠讀到這個 Cookie。 var allCookie = document.cookie;
注意,這種方法只適用於 Cookie 和 iframe 窗口,LocalStorage 和 IndexDB 沒法經過這種方法,規避同源政策,而要使用下文介紹的PostMessage API。
另外,服務器也能夠在設置Cookie的時候,指定Cookie的所屬域名爲一級域名,好比.example.com。
Set-Cookie: key=value; domain=.example.com; path=/ //這樣的話,二級域名和三級域名不用作任何設置,均可以讀取這個Cookie。
不一樣的iframe 之間(父子或同輩),是可以獲取到彼此的window對象的,可是你卻不能使用獲取到的window對象的屬性和方法(html5中的postMessage方法是一個例外,還有些瀏覽器好比ie6也可使用top、parent等少數幾個屬性),總之,你能夠當作是隻能獲取到一個幾乎無用的window對象。
首先說明一下同域之間的iframe是能夠操做的。好比http://127.0.0.1/JSONP/a.html裏面嵌入一個iframe指向http://127.0.0.1/myPHP/b.html。那麼在a.html裏面是能夠操做iframe裏面的DOM的。
<iframe src="http://127.0.0.1/myPHP/b.html" frameborder="1"></iframe> <body> <script type="text/javascript"> var iframe = document.querySelector("iframe"); iframe.onload = function(){ var win = iframe.contentWindow; var doc = win.document; var ele = doc.querySelector(".text1"); var text = ele.innerHTML="123456"; } </script>
若是兩個網頁不一樣源,就沒法拿到對方的DOM。典型的例子是iframe窗口和window.open方法打開的窗口,它們與父窗口沒法通訊。若是兩個窗口一級域名相同,只是二級域名不一樣,那麼document.domain屬性,就能夠規避同源政策,拿到DOM。
對於徹底不一樣源的網站,目前有三種方法,能夠解決跨域窗口的通訊問題。
(3)使用片斷識別符來進行跨域
片斷標識符(fragment identifier)指的是,URL的#號後面的部分,好比http://example.com/x.html#fragment的#fragment。若是隻是改變片斷標識符,頁面不會從新刷新。
父窗口能夠把信息,寫入子窗口的片斷標識符。在父窗口寫入:
document.getElementById('frame').onload = function(){ var src = "http://127.0.0.1/JSONP/b.html" + '#' + "data"; this.src = src; }
子窗口經過監聽hashchange事件獲得通知。
window.onload = function(){ console.log("b.html加載完成") window.onhashchange = function(){ var message = window.location.hash; console.log(message)//#data }; }
一樣的,子窗口也能夠改變父窗口的片斷標識符。
parent.location.href= target + "#" + hash;
(4)使用window.name來進行跨域
window對象有個name屬性,該屬性有個特徵:即在一個窗口(window)的生命週期內,窗口載入的全部的頁面都是共享一個window.name的,每一個頁面對window.name都有讀寫的權限,window.name是持久存在一個窗口載入過的全部頁面中的,並不會因新頁面的載入而進行重置。這個屬性的最大特色是,不管是否同源,只要在同一個窗口裏,前一個網頁設置了這個屬性,後一個網頁能夠讀取它。
好比:有一個頁面a.html,它裏面有這樣的代碼:
window.name = "我是a頁面設置的"; setTimeout(function(){ window.location = "http://127.0.0.1/JSONP/b.html"; },1000)
b.html頁面的代碼:
console.log(window.name);
a.html頁面載入後1秒,跳轉到了b.html頁面,結果b頁面打印出了:
我是a頁面設置的
能夠看到在b.html頁面上成功獲取到了它的上一個頁面a.html給window.name設置的值。若是在以後全部載入的頁面都沒對window.name進行修改的話,那麼全部這些頁面獲取到的window.name的值都是a.html頁面設置的那個值。固然,若是有須要,其中的任何一個頁面均可以對window.name的值進行修改。注意,window.name的值只能是字符串的形式,這個字符串的大小最大能容許2M左右甚至更大的一個容量,具體取決於不一樣的瀏覽器,但通常是夠用了。
利用window.name能夠對同域或者不一樣域的之間的js進行交互。
那麼在a.html頁面中,咱們怎麼把b.html頁面載入進來呢?顯然咱們不能直接在a.html頁面中經過改變window.location來載入b.html頁面,由於咱們想要即便a.html頁面不跳轉也能獲得b.html裏的數據。答案就是在a.html頁面中使用一個隱藏的iframe來充當一箇中間人角色,由iframe去獲取b.html的數據,而後a.html再去獲得iframe獲取到的數據。
(5)window.postMessage
上面兩種方法都屬於破解,HTML5爲了解決這個問題,引入了一個全新的API:跨文檔通訊 API(Cross-document messaging)。
這個API爲window對象新增了一個window.postMessage方法,容許跨窗口通訊,不論這兩個窗口是否同源。目前IE8+、FireFox、Chrome、Opera等瀏覽器都已經支持window.postMessage方法。
舉例來講,父窗口http://a.com向子窗口http://b.com發消息,調用postMessage方法就能夠了。
a頁面:
<iframe id="frame1" src="http://127.0.0.1/JSONP/b.html" frameborder="1"></iframe> document.getElementById('frame1').onload = function(){ var win = document.getElementById('frame1').contentWindow; win.postMessage("我是來自a頁面的","http://127.0.0.1/JSONP/b.html") }
b頁面經過監聽message事件能夠接受到來自a頁面的消息。
window.onmessage = function(e){
e = e || event;
console.log(e.data);//我是來自a頁面的
}
子窗口向父窗口發送消息的寫法相似。
window.opener.postMessage('我是來自b頁面的', 'http://a.com'); //父窗口和子窗口均可以經過message事件,監聽對方的消息。
經過window.postMessage,讀寫其餘窗口的 LocalStorage 也成爲了可能。
下面是一個例子,主窗口寫入iframe子窗口的localStorage。
父窗口發送消息代碼
var win = document.getElementsByTagName('iframe')[0].contentWindow; var obj = { name: 'Jack' }; // 存入對象 win.postMessage(JSON.stringify({key: 'storage', method: 'set', data: obj}), 'http://b.com'); // 讀取對象 win.postMessage(JSON.stringify({key: 'storage', method: "get"}), "*"); window.onmessage = function(e) { if (e.origin != 'http://a.com') return; // "Jack" console.log(JSON.parse(e.data).name); };
子窗口接收消息的代碼
window.onmessage = function(e) { if (e.origin !== 'http://bbb.com') return; var payload = JSON.parse(e.data); switch (payload.method) { case 'set': localStorage.setItem(payload.key, JSON.stringify(payload.data)); break; case 'get': var parent = window.parent; var data = localStorage.getItem(payload.key); parent.postMessage(data, 'http://aaa.com'); break; case 'remove': localStorage.removeItem(payload.key); break; } };
(6)經過WebSocket進行跨域
WebSocket是一種通訊協議,使用ws://(非加密)和wss://(加密)做爲協議前綴。該協議不實行同源政策,只要服務器支持,就能夠經過它進行跨源通訊。
下面是一個例子,瀏覽器發出的WebSocket請求的頭信息
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13 Origin: http://example.com
上面代碼中,有一個字段是Origin,表示該請求的請求源(origin),即發自哪一個域名。
正是由於有了Origin這個字段,因此WebSocket纔沒有實行同源政策。由於服務器能夠根據這個字段,判斷是否許可本次通訊。若是該域名在白名單內,服務器就會作出以下回應。
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chat
(7)CORS
CORS是跨源資源分享(Cross-Origin Resource Sharing)的縮寫。它是W3C標準,是跨源AJAX請求的根本解決方法。相比JSONP只能發GET請求,CORS容許任何類型的請求。CORS須要瀏覽器和服務器同時支持。目前,全部瀏覽器都支持該功能,IE瀏覽器不能低於IE10。
整個CORS通訊過程,都是瀏覽器自動完成,不須要用戶參與。對於開發者來講,CORS通訊與同源的AJAX通訊沒有差異,代碼徹底同樣。瀏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感受。
所以,實現CORS通訊的關鍵是服務器。只要服務器實現了CORS接口,就能夠跨源通訊。
(8)服務端設置代理頁面專門處理前端跨域請求
總結:以上整理了各類常見的跨域解決辦法,在開發過程當中咱們能夠根據不一樣的場景選擇最佳的解決辦法。處理ajax的跨域能夠選擇JSONP、CORS,服務端設置代理、WebSocket。若是主域相同,處理多級子域之間的通訊能夠選擇document.domain,處理不一樣域之間的iframe,子窗口能夠選擇window.name、window.postMessage、location.hash來解決。
5、怎麼發送一個跨域的POST請求?
6、跨域方式
var script = document.createElement('script'); script.type = 'text/javascript'; // 傳參並指定回調執行函數爲onBack script.src = 'http://www.domain-com:8080/login?user=admin&callback=onBack'; document.head.appendChild(script); // 回調執行函數 function onBack(res) { alert(JSON.stringify(res)); }
//父窗口:(http://www.domain.com/a.html) <iframe id="iframe" src="http://child.domain.com/b.html"></iframe> <script> document.domain = 'domain.com'; var user = 'admin'; </script> //子窗口:(http://child.domain.com/b.html) <script> document.domain = 'domain.com'; // 獲取父窗口中變量 alert('get js data from parent ---> ' + window.parent.user); //"admin" </script>
location.hash + iframe
與document.domain相似,不一樣的是,經過修改父頁面的iframe的src進而達到修改window.hash的效果,子頁面經過window.onhashchange來監聽
跨域資源共享(CORS)
前端正常請求,後端設置:
res.writeHead(200, { 'Access-Control-Allow-Credentials': 'true', // 後端容許發送Cookie 'Access-Control-Allow-Origin': 'http://www.domain1.com', // 容許訪問的域(協議+域名+端口) /* * 此處設置的cookie仍是domain2的而非domain1,由於後端也不能跨域寫cookie(nginx反向代理能夠實現), * 但只要domain2中寫入一次cookie認證,後面的跨域接口都能從domain2中獲取cookie,從而實現全部的接口都能跨域訪問 */ 'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly的做用是讓js沒法讀取cookie });