前端總結(二)跨域

參考: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。 


對於徹底不一樣源的網站,目前有三種方法,能夠解決跨域窗口的通訊問題。

  1. 片斷識別符(fragment identifier)
  2. window.name
  3. 跨文檔通訊API(Cross-document messaging)

(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、跨域方式

 

  • 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)); } 
  • document.domain + iframe跨域
    //父窗口:(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來監聽

  • window.name + iframe跨域
  • 跨域資源共享(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 }); 
  • nginx代理跨域
  • nodejs中間件代理跨域
  • WebSocket協議跨域
相關文章
相關標籤/搜索