1- 同源策略:
- 首先理解什么叫同源
同源指的是協(xié)議、域名、端口都必須一致。只要其中一個不一致都不是同源。 - 瀏覽器中出于對安全的考慮。只允許本域下的接口交互。不同源的客戶端腳本在沒有明確授權(quán)的情況下,不能讀取對方的資源。
- 對于當(dāng)前頁面來說頁面中 JS 文件的域不重要,重要的是當(dāng)前頁面所在的域與 腳本中涉及到的域(例如xht的open方法的url)是否同源
2- 關(guān)于跨域,跨域的幾種實現(xiàn)形式
- 跨域:瀏覽器出于安全方面的考慮設(shè)置了同源策略來限制不同源之間的交互,但是也阻礙了不同域之間的協(xié)助。為了實現(xiàn)不同源之間的交互、協(xié)作,因此需要“跨域”。
- 跨域的幾種實現(xiàn)形式:
- JSONP(利用src屬性)
- CORS(跨源資源共享,//iE10及以上支持)
- 降域 (具有局限性,只有是同屬于一個域名的二級域名還能夠使用這種方式)
- postMessage
3- JSONP 的原理:
- 基本思想是,網(wǎng)頁通過添加一個<script>元素,向服務(wù)器請求JSON數(shù)據(jù),這種做法不受同源政策限制;服務(wù)器收到請求后,將數(shù)據(jù)放在一個指定名字的回調(diào)函數(shù)里傳回來。傳回后回調(diào)函數(shù)立即執(zhí)行(參數(shù)是后端產(chǎn)生的數(shù)據(jù)),從而實現(xiàn)相應(yīng)的功能。
- 具體流程:
1.網(wǎng)頁動態(tài)地插入<script>元素,由它向跨域網(wǎng)址發(fā)出請求
function addScriptTag(src){
var script = document.createElement('script');
script.src = src; //跨域網(wǎng)址
document.body.appendChild(script);
// 往頁面插入元素后,會向跨域網(wǎng)址發(fā)出請求(src指定了跨域網(wǎng)址,得到響應(yīng)后立即執(zhí)行
}
window.onload = function(){
addScriptTag("http://example.com/ip?callback = foo'); //當(dāng)頁面加載完畢,即往頁面中插入script元素
}
function foo(data){
console.log('your public ip address is: '+ data.ip)'
}
2.上面代碼通過動態(tài)添加<script>元素,向服務(wù)器example.com發(fā)出請求。注意,該請求的查詢字符串有一個callback參數(shù),用來指定回調(diào)函數(shù)的名字,這對于JSONP是必需的。
3.服務(wù)器收到該請求后,會將數(shù)據(jù)放在回到函數(shù)的參數(shù)位置返回
foo({
"ip": "8.8.8.8"
});
4.由于<script>元素請求的腳本,直接作為代碼運行。這時,只要瀏覽器定義了foo函數(shù),該函數(shù)就會立即調(diào)用。作為參數(shù)的JSON數(shù)據(jù)被視為JavaScript對象,而不是字符串,因此避免了使用JSON.parse的步驟。
- 注意:JSONP只能發(fā) GET 請求(因為請求是放在<script>的scr中的)。
4- 關(guān)于 CORS
CORS: Cross-Origin Resource Sharing, 跨源資源共享,它是一種 ajax跨域請求資源的方式,支持現(xiàn)代瀏覽器,IE支持10以上。
實現(xiàn)過程:當(dāng)使用 XMLHttpRequest發(fā)送請求時,瀏覽器發(fā)現(xiàn)該請求不符合同源策略,會自動給該請求加一個請求頭:Origin,并將請求發(fā)送。服務(wù)器端收到請求后,如果確定接受請求則在返回結(jié)果中加入一個響應(yīng)頭:Access-Control-Allow-Origin; 瀏覽器收到響應(yīng)后判斷該相應(yīng)頭中是否包含 Origin 的值,如果有則瀏覽器會處理響應(yīng),我們就可以拿到響應(yīng)數(shù)據(jù),如果不包含瀏覽器直接駁回,這時我們無法拿到響應(yīng)數(shù)據(jù)。
整個CORS通信過程,都是瀏覽器自動完成,不需要用戶參與
實現(xiàn)CORS通信的關(guān)鍵是服務(wù)器。只要服務(wù)器實現(xiàn)了CORS接口,就可以跨源通信。
詳細流程:
1.瀏覽器發(fā)現(xiàn)這次請求不符合同源策略,就自動在頭信息之中,添加一個Origin字段。
GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
在上面的頭信息中,Origin字段表明了本次請求來自哪個源:協(xié)議、域名、端口。
2.該請求到達服務(wù)器后,服務(wù)器會根據(jù)這個值來判斷是否接受請求。如果Origin指定的域名在許可范圍內(nèi),服務(wù)器返回的響應(yīng),會多出幾個頭信息字段(如下所示)。如果Origin指定的源,不在許可范圍內(nèi),服務(wù)器會返回一個正常的HTTP回應(yīng)。
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
3.瀏覽器收到響應(yīng)后判斷該相應(yīng)頭中是否包含Origin的值,如果響應(yīng)的頭信息沒有包含Access-Control-Allow-Origin字段,就知道出錯了,從而拋出一個錯誤,被XMLHttpRequest的onerror回調(diào)函數(shù)捕獲,這時我們無法拿到響應(yīng)數(shù)據(jù)。如果有則瀏覽器會處理響應(yīng),我們就可以拿到響應(yīng)數(shù)據(jù)。
5- 降域
- 降域獲取同一Cookie:Cookie 是服務(wù)器寫入瀏覽器的一小段信息,只有同源的網(wǎng)頁才能共享。但是,兩個網(wǎng)頁一級域名相同,只是二級域名不同,瀏覽器允許通過設(shè)置document.domain共享 Cookie。
- example:A網(wǎng)頁是http://w1.example.com/a.html,B網(wǎng)頁是http://w2.example.com/b.html,那么只要設(shè)置相同的document.domain,兩個網(wǎng)頁就可以共享Cookie。
// A網(wǎng)頁和B網(wǎng)頁設(shè)置相同的document.domaindocument.domain = 'example.com'
// A網(wǎng)頁通過腳本設(shè)置Cookiedocument.cookie = "test1 = hello";
// B網(wǎng)頁可以獲取到該cookievar otherCookie = document.cookie;
- 降域使不同源的iframe窗口和父窗口相互通信:如果兩個網(wǎng)頁不同源,就無法拿到對方的DOM。典型的例子是iframe窗口和與父窗口無法通信。如果兩個窗口一級域名相同,只是二級域名不同,那么設(shè)置document.domain屬性,就可以規(guī)避同源政策,拿到DOM。
A 網(wǎng)頁
URL: http://a.yanxin.com:8080/a.html
<div class="ct">
<h1>使用降域?qū)崿F(xiàn)跨域</h1>
<div class="main">
<input type="text" placeholder="http://a.yanxin.cn:8080/a.html">
</div>
<iframe src="http://b.yanxin.com:8080/b.html" frameborder="0" ></iframe>
</div>
<script>
document.querySelector('.main input').addEventListener('input', function(){
console.log(this.value);
/* window.frames 是窗口中所有命名的框架組成的數(shù)組。
這個數(shù)組的每個元素都是一個Window對象,對應(yīng)于窗口中的一個框架。
window.frames[0]得到的就是html中的框架
window.frames[0].document.querySelector('input') 得到框架中的input元素
*/
window.frames[0].document.querySelector('input').value = this.value;})
document.domain = "yanxin.com"
</script>
iframe中的B網(wǎng)頁
URL: http://b.yanxin.com:8080/b.html
<input id="input" type="text" placeholder="http://b.yanxin.com:8080/b.html"><script>
document.querySelector('#input').addEventListener('input', function(){
// 得到父窗口中的input元素
window.parent.document.querySelector('input').value = this.value;
})
document.domain = 'yanxin.com';
</script>
6- postMessage
HTML5為了解決跨域問題,引入了一個全新的API:跨文檔通信 API(Cross-document messaging)。
這個API為window對象新增了一個window.postMessage方法,允許跨窗口通信,不論這兩個窗口是否同源。目的:向另一個地方傳遞數(shù)據(jù),另一個地方指的是:包含在當(dāng)前頁面的<iframe>元素,或者由當(dāng)前頁面彈出的窗口
-
window.postMessage() 方法被調(diào)用時,會在所有頁面腳本執(zhí)行完畢之后向目標(biāo)窗口派發(fā)一個 MessageEvent 消息。 * otherWindow.postMessage(message, targetOrigin, [transfer]);
- otherWindow:其他窗口的一個引用(相對于當(dāng)前的窗口的其他窗口),比如iframe的contentWindow屬性、執(zhí)行window.open返回的窗口對象、或者是命名過或數(shù)值索引的window.frames。
- message將要發(fā)送到其他 window的數(shù)據(jù)
- targetOrigin:指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示無限制)或者一個URI。在發(fā)送消息的時候,如果目標(biāo)窗口的協(xié)議、主機地址或端口這三者的任意一項不匹配targetOrigin提供的值,那么消息就不會被發(fā)送;只有三者完全匹配,消息才會被發(fā)送。
- 注意:如果你明確的知道消息應(yīng)該發(fā)送到哪個窗口,那么請始終提供一個有確切值的targetOrigin,而不是*。不提供確切的目標(biāo)將導(dǎo)致數(shù)據(jù)泄露到任何對數(shù)據(jù)感興趣的惡意站點。
示例
頁面A: http://a.yanxin.cn:8080/a.html
在頁面A中打開頁面B: http://b.yanxin.cn:8080/b.html
當(dāng)點擊頁面A上的button時,向頁面B傳輸消息"hello world"
<button id="sendmessage">send message</button>
<script>
var button = document.querySelector("#sendmessage");
// 打開頁面b,并且取得對它的引用
var targetWindow = window.open("http://b.yanxin.cn:8080/b.html");
button.addEventListener('click',function(){
// 注意: 這里是向targetWindow即新打開的窗口(通過上面的window.open打開的窗口)發(fā)送消息
// 直接打開頁面b(手動打開的)是無法收到消息的
// 如果直接調(diào)用postMessage則相當(dāng)于當(dāng)前窗口向自己發(fā)送消息
targetWindow.postMessage("hello world!!", "http://b.yanxin.cn:8080/b.html");
});
</script>
為頁面B的window添加監(jiān)聽器
當(dāng)頁面B收到Message時,在控制臺中輸出收到的消息,并提示"hello"
<script>
window.addEventListener("message", receiveMessage);
function receiveMessage(event){
console.log(event.data);
alert("hello");
}
</script>
參考資料:
- CORS 詳解-阮一峰
- 瀏覽器同源政策及其規(guī)避方法-阮一峰
- api.jirengu.com里封裝的接口都支持跨域調(diào)用,可以使用這些接口做一些有意思的應(yīng)用
- 參考代碼
視頻代碼
文本資料