跨域方式及其實現

產生原因

爲什麼會產生跨域呢,因爲瀏覽器爲了安全採用了一系列的安全機制,其中有一個是同源策略。何爲同源策略(same-origin policy)。簡單來講同源策略就是瀏覽器爲了保證用戶信息的安全,防止惡意的網站竊取數據,禁止不同域之間的JS進行交互。對於瀏覽器而言只要域名、協議、端口其中一個不同就會引發同源策略。

關於瀏覽器安全和同源策略詳解 https://www.yuque.com/suihangadam/liulanqi/pog4pf

爲何要跨域

公司內部可能有多個不同的子域,比如一個是location.company.com ,而應用是放在app.company.com , 這時想從 app.company.com去訪問 location.company.com 的資源就屬於跨域。

跨域方式

JSONP

jsonp是一種非正式的傳輸協議

跨域原理: 利用了src不受同源策略的影響 ,可以訪問其他頁面的數據。

注意⚠️:jsonp並不能解決所有的跨域問題,因爲使用jsonp跨域需要被提供jsonp接口

// 1.創建一個全局函數
function callBack (data) {
    console.log(data);
}
// 2.動態創建一個script標籤
var currentScript = document.createElement("script");
// 3.給標籤的src賦值(即接口的url),並將函數附加到url上,注意:大部分jonsp接口都爲callback,百度的jsonp接口爲cb
currentScript.src = "http:www.baidu.com?a=1&b=2&cb=callBack";
// 4.將標籤插入到頁面上
document.body.appendChild(script1);
// 5.將標籤加載完後刪除 
script1.onload = function(){
    this.remove()
}

修改document.domain來跨子域

跨域原理:兩個網頁一級域名相同,只是二級域名不同,瀏覽器允許通過設document.domain共享 Cookie或者處理iframe。

注意⚠️:用來處理Cookie 和 iframe,

處理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

不同的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。 

window.name + iframe

跨域原理:即在一個窗口(window)的生命週期內,窗口載入的所有的頁面都是共享一個window.name的,每個頁面window.name都有讀寫的權限。

注意⚠️:是在一個窗口!並且window.name的值只能是字符串的形式,這個字符串的大小最大能允許2M左右甚至更大的一個容量,具體取決於不同的瀏覽器。

簡單栗子🌰

1.創建一個a.html

<script>
    window.name = "哈哈,我是頁面a設置的值喲!";
    //設置window.name的值
    setTimeout(function(){
        window.location = 'b.html';
    },3000);//3秒後把一個新頁面載入當前window
</script>

2.在b.html讀取

<script>
    alert(window.name);//讀取window.name的值
</script>
3.a.html載入3秒後,跳轉到b.html頁面中,結果爲

image.png

跨域栗子🌰

比如:有一個example.com/a.html頁面,需要通過a.html頁面裏的js來獲取另一個位於不同域上的頁面cnblogs.com/data.html裏的數據。

1.創建cnblogs.com/data.html代碼

<script>
    window.name = "我是data.html的數據,所有可以轉化爲字符串來傳遞的數據都可以在這裏使用,比如這裏可以傳遞Json數據";
</script>

2.創建example.com/a.html的代碼

想要即使a.html頁面不跳轉也能得到data.html裏的數據。在a.html頁面中使用一個隱藏的iframe來充當一箇中間人角色,由iframe去獲取data.html的數據,然後a.html再去得到iframe獲取到的數據。

<iframe id = "iframe" src = "cnblogs.com/data.html" style = "display:none" onload = "getData()"></iframe>
<script>
    function getData(){
    //iframe載入data.html頁面會執行此函數
        var ifr = document.getElementById("iframe");
        ifr.onload = function(){
        //這個時候iframe和a.html已經處於同一源,可以互相訪問
            var data = ifr.contentWindow.name;
//獲取iframe中的window.name,也就是data.html中給它設置的數據
            alert(data);
        }
        ifr.src = 'b.html';//這裏的b.html爲隨便一個頁面,只要與a.html同源就行,目的是讓a.html能夠訪問到iframe中的東西,否則訪問不到
    }
</script>

window.postMessage + iframe

跨域原理:可以使用window.postMessage()來向其它的window對象發送消息,無論這個window對象是屬於同源或不同源(可實現跨域),其他的頁面通過window.onmessage來接收。

注意⚠️:window.postMessage(message,targetOrigin) 方法是html5新引進的特性,目前IE8+、FireFox、Chrome、Opera等瀏覽器都已經支持window.postMessage方法。但IE6、IE7不支持

message:爲要發送的消息,類型只能爲字符串;

targetOrigin:用來限定接收消息的那個window對象所在的域,如果不想限定域,可以使用通配符 “*”。

栗子🌰

1.創建www.test.com/a.html頁面代碼

<iframe id="iframe" src="www.script.com/b.html" onload="onLoad()"></iframe>
<script>
function onLoad(){
    var iframe = document.getElementById("iframe");
    var win = iframe.contentWindow;
    win.postMessage('哈哈,我是來自頁面a.html的信息喲!','*');//向不同域的www.script.com/b.html發送消息
}
</script>

2.創建www.script.com/b.html頁面代碼

<script>
window.onmessage = function(e){ //註冊message時間來接收消息
    e = e || event;             //獲取時間對象
    alert(e.data);              //通過data屬性來得到傳送的消息
}
</script>

使用location.hash+iframe

跨域原理:使用location.hash和iframe來繞過同源策略

注意⚠️:需要借用兩個iframe

假設域名test.com下的文件a.html要和csdnblogs.com域名下的b.html傳遞信息

1.創建test.com下的a.html頁面, 同時在a.html上加一個定時器,隔一段時間來判斷location.hash的值有沒有變化,一旦有變化則獲取獲取hash值

www.test.com下a.html代碼

<script>
function startRequest(){
    var ifr = document.createElement('iframe');
    //創建一個隱藏的iframe
    ifr.style.display = 'none';
    ifr.src = 'http://www.csdnblogs.com/b.html#paramdo';
    //傳遞的location.hash 
    document.body.appendChild(ifr);
}

function checkHash() {
    try {
        var data = location.hash ? location.hash.substring(1):'';
        if (console.log) {
            console.log('Now the data is ' + data);
        }
    } catch (e) {};
}
setInterval(checkHash, 5000);
window.onload = startRequest;
</script>

2.b.html響應請求後再將通過修改test.com域名下的文件c.html來間接a.html的hash值來傳遞數據,代碼如下:

www.csdnblogs.com域名下b.html代碼

<script>
function checkHash() {
    var data = '';
    //模擬一個簡單的參數處理操作
    switch (location.hash) {
        case '#paramdo':
            data = 'somedata';
            break;
        case '#paramset':
             //do something……
            break;
        default:
            break;
    }
    data && callBack('#' + data);
}

function callBack(hash) {
    // ie、chrome的安全機制無法修改parent.location.hash
    //所以要利用一箇中間的www.csdnblogs.com域下的代理iframe
    var proxy = document.createElement('iframe');
    proxy.style.display = 'none';
    proxy.src = 'http://www.csdnblogs.com/c.html' + hash; 
    // 注意該文件在"www.test.com"域下
    document.body.appendChild(proxy);
}
window.onload = checkHash;
</script>

www.test.com下c.html代碼

<script>
//因爲parent.parent和自身屬於同一個域,所以可以改變其location.hash的值
parent.parent.location.hash = self.location.hash.substring(1);
</script>

跨域資源共享(CORS)

跨域原理:一種跨域訪問的機制,可以讓AJAX實現跨域訪問;CORS允許一個域上的網絡應用向另一個域提交跨域AJAX請求。

注意⚠️:

  • 只有服務器設置Access-Control-Allow-Origin HTTP響應頭之後,瀏覽器將會允許跨域請求。就是使用自定義的HTTP頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應是應該成功,還是應該失敗。
  • CORS機制是同源策略出讓的安全性之一。其餘兩個爲允許嵌入第三方資源和跨文檔消息機制(window.postmessage)。詳情:https://www.yuque.com/suihangadam/liulanqi/pog4pf

IE中對CORS的實現是通過xdr

var xdr = new XDomainRequest();
xdr.onload = function(){
    console.log(xdr.responseText);
}
xdr.open('get', 'http://www.test.com');
......
xdr.send(null);

其它瀏覽器中的實現就在xhr中

var xhr =  new XMLHttpRequest();
xhr.onreadystatechange = function () {
  if(xhr.readyState === 4 && xhr.status === 200){
        console.log(xhr.responseText);
        } 
    }
}
xhr.open('get', 'http://www.test.com');
......
xhr.send(null);

實現跨瀏覽器的CORS

function createCORS(method, url){
    var xhr = new XMLHttpRequest();
    if('withCredentials' in xhr){
        xhr.open(method, url, true);
    }else if(typeof XDomainRequest != 'undefined'){
        var xhr = new XDomainRequest();
        xhr.open(method, url);
    }else{
        xhr = null;
    }
    return xhr;
}
var request = createCORS('get', 'http://www.test.com');
if(request){
    request.onload = function(){
        ......
    };
    request.send();
}

Web sockets

跨域原理: 是一種瀏覽器的API,它的目標是在一個單獨的持久連接上提供全雙工、雙向通信。(同源策略對web sockets不適用),在JS創建了web socket之後,會有一個HTTP請求發送到瀏覽器以發起連接。取得服務器響應後,建立的連接會使用HTTP升級從HTTP協議交換爲web sockt協議。

<script>
  var socket = new WebSockt('ws://www.test.com');
  //http->ws; https->wss
  socket.send('hello WebSockt');
  socket.onmessage = function(event){
      var data = event.data;
  }
</script>

使用nginx進行反向代理

跨域原理:Nginx反向代理將對真實服務器的請求轉移到本機服務器來避免瀏覽器的"同源策略限制"。

Nginx是一個高性能的HTTP和反向代理web服務器

image.png

注意⚠️: 反向代理隱藏了真實的服務器

擴展:正向代理隱藏了真實的客戶端。

Nginx 反向代理模塊 proxy_pass

proxy_pass 後面跟着一個 URL,用來將請求反向代理到 URL 參數指定的服務器上。例如我們上面例子中的 proxy_pass https://api.shanbay.com,則將匹配的請求反向代理到 https://api.shanbay.com

通過在配置文件中增加proxy_pass 你的服務器ip,就可以完成反向代理。

server {
    listen       80;
    server_name  localhost;
    ## 用戶訪問 localhost,則反向代理到https://api.shanbay.com
    location / {
        root   html;
        index  index.html index.htm;
        proxy_pass https://api.shanbay.com;
    }
}

nginx反向代理詳細配置:https://www.cnblogs.com/ddlove/p/9945988.html

使用webpack配置代理

前提條件

1、需要使用本地開發插件:webpack-dev-server

2、webpack-dev-server使用的是http-proxy-middleware來實現跨域代理的。

module.exports = {
  //...
  devServer: {
    proxy: {
      '/api': {
        target: 'http://www.baidu.com/',
        pathRewrite: {'^/api' : ''},
        changeOrigin: true,     // target是域名的話,需要這個參數,
        secure: false,          // 設置支持https協議的代理
      },
      '/api2': {
          .....
      }
    }
  }
};

參考自

百度百科

https://blog.csdn.net/wangchengiii/article/details/78081032

https://www.cnblogs.com/mmy67/p/10032915.html

https://www.cnblogs.com/mmy67/p/10032915.html

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章