理解跨域及經常使用解決方案

跨域,相信你們不管是在工做中仍是在面試中常常遇到這個問題,經常在網上看到別人所整理的一些方法,看似知道是怎麼回事,但若是沒有動手實踐過,總以爲本身沒有真正的掌握,在這裏,經過本身認真思考整理一些經常使用的方法。

跨域的產生

不用多講,做爲一名前端開發人員,相信你們都知道跨域是由於瀏覽器的同源策略所致使的。所謂同源是指"協議+域名+端口"三者相同,即使兩個不一樣的域名指向同一個ip地址,也非同源。瀏覽器引入同源策略主要是爲了防止XSS,CSRF攻擊。javascript

CSRF(Cross-site request forgery),跨站請求僞造,也被稱爲:one click attack/session riding,縮寫爲:CSRF/XSRF。

在同源策略影響下,域名A向域名B發送Ajax請求,或操做Cookie、LocalStorage、indexDB等數據,或操做dom,js就會受到限制,但請求css,js等靜態資源不受限制css

跨域的解決方案

1 經過jsonp跨域

首先說一下jsonp的原理,例如咱們平時寫html的時候經常會使用
<script src="www.b.com/js/jquery.js"></script>這種方式去取放在另外服務器上的靜態資源,這個是不受同源策略所限制的,因此咱們利用這一點能夠解決跨域的問題。html

主要代碼以下:前端

1.1原生實現

在www.a.com域名寫下以下代碼,去請求www.b.com域名的數據
&lt;script&gt;
    var script = document.creatElement('script');
    script.type = 'text/javascript';
    script.src = 'http://www.b.com/getdata?callback=demo';
    
    function demo(res){
      console.log(res);
    }
&lt;/script&gt;

這裏,咱們利用動態腳本的src屬性,變相地發送了一個http://www.b.com/getdata?call...。這時候,b.com頁面接受到這個請求時,若是沒有JSONP,會正常返回json的數據結果,像這樣:{ msg: 'helloworld' },而利用JSONP,服務端會接受這個callback參數,而後用這個參數值包裝要返回的數據:demo({msg: 'helloworld'});html5

這時候,若是a.com的頁面上正好有一個demo 的函數:java

function demo(res){node

console.log(res);

}jquery

當遠程數據一返回的時候,隨着動態腳本的執行,這個demo函數就會被執行。nginx

1.2 jquery ajax請求實現

$.ajax({
    url:'http://www.b.com/getdata',
    type:'get',
    dataType: 'jsonp',  // 請求方式爲jsonp
    jsonpCallback: 'demo', // 自定義回調函數名
    data: {}
});

服務端代碼實現:

以nodejs爲例web

var http = require(http);
//引入url模塊解析url字符串
var url = require('url);
//引入querystring模塊處理query字符串
var querystring = require('querystring');

var server = http.createServer();

server.on('request',function(req,res){
    var urlPath = url.parse(req.url).pathname;
    var param = querystring .parse(req.url.split('?')[1]);
    
    if(urlPath === '/getData' &amp;&amp; param.callback) {
    
        res.writeHead(200,{'Content-Type','application/json;charset=utf-8'});
        
        var data = { msg: 'helloworld' };
        data = JSON.stringify(data );
        
        var callback = param .callback+'('+data+');';
        res.write(callback);
        
        res.end();
    } else {
        res.writeHead(200, {'Content-Type':'text/html;charset=utf-8'});
        
        res.write('Hell World\n');
        res.end();    
    
    }


})
jsonp缺點:只能使用get請求,不推薦使用

2 CORS 跨域資源共享

跨域資源共享(CORS) 是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器 讓運行在一個 origin (domain) 上的Web應用被准許訪問來自不一樣源服務器上的指定的資源。當一個資源從與該資源自己所在的服務器不一樣的域或端口請求一個資源時,資源會發起一個跨域 HTTP 請求。

Cross-Origin Resource Sharing跨域資源共享,應該算是如今比較推薦的跨域處理方案.不只適用於各類Method,並且更加方便和簡單
目前,全部瀏覽器都支持該功能,IE瀏覽器不能低於IE10。

2.1 簡單請求和非簡單請求

瀏覽器將CORS請求分紅兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。

簡單請求同時知足如下條件,只要不知足如下條件的則爲非簡單請求

非簡單請求會發出一次預檢測請求,返回碼是204,預檢測經過纔會真正發出請求,這才返回200。這裏經過前端發請求的時候增長一個額外的headers來觸發非簡單請求。

2.2 進行帶有身份憑證的CORS 請求

  • 默認狀況下的跨域請求都是不會把cookie發送給服務器的,在須要發送的狀況下,若是是xhr,那麼須要設置xhr.withCredentials=true,
  • 若是是採用fetch獲取的話,那麼須要在request裏面設置 credentials:'include',
  • 可是若是服務器在預請求的時候沒返回Access-Control-Allow-Crenditials:true的話,那麼在實際請求的時候,cookie是不會被髮送給服務器端的,要特別注意對於簡單的get請求,不會有預請求的過程,
  • 那麼在實際請求的時候,若是服務器沒有返回Access-Control-Allow-Crenditials:true的話那麼響應結果瀏覽器也不會交給請求者
對於附帶身份憑證的請求,服務器不得設置 Access-Control-Allow-Origin 的值爲「*」。

這是由於請求的首部中攜帶了 Cookie 信息,若是 Access-Control-Allow-Origin
的值爲「*」,請求將會失敗。而將 Access-Control-Allow-Origin 的值設置爲
http://www.a.com,則請求將成功執行。

2.3 HTTP 響應首部字段

  • Access-Control-Allow-Origin: <origin> | *
  • Access-Control-Expose-Headers 頭讓服務器把容許瀏覽器訪問的頭放入白名單
  • Access-Control-Max-Age 頭指定了preflight請求的結果可以被緩存多久
  • Access-Control-Allow-Credentials
    頭指定了當瀏覽器的credentials設置爲true時是否容許瀏覽器讀取response的內容。
  • Access-Control-Allow-Methods 首部字段用於預檢請求的響應。其指明瞭實際請求所容許使用的 HTTP 方法。
  • Access-Control-Allow-Headers 首部字段用於預檢請求的響應。其指明瞭實際請求中容許攜帶的首部字段。

2.4 以nodejs express爲例,說明如何使用cors解決跨域

var express=require('express');
var url=require('url');
var app=express();
var allowCrossDomain = function(req, res, next) {
    res.header('Access-Control-Allow-Origin', 'http://localhost:63342');
    res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
    res.header('Access-Control-Allow-Headers', 'Content-Type');
    res.header('Access-Control-Allow-Credentials','true');
    next();
};
app.use(allowCrossDomain);
app.get('/getData',function (req,res,next) {
    var queryValue=url.parse(req.url).query;
    if(queryValue==='fortunewheel@sina.com'){
        res.send(true);
    }else {
        res.send(false);
    }

});
app.listen(3001);
實際開發過程當中,爲了安全,會和token一塊兒使用

3 window.postMessage

postMessage是HTML5 XMLHttpRequest Level 2中的API,且是爲數很少能夠跨域操做的window屬性之一,它可用於解決如下方面的問題:

  • iframe嵌套頁面跨域通訊
  • 頁面和其打開的新窗口的通訊
  • 多窗口之間消息傳遞

用法:
postMessage(data,origin)方法接受兩個參數,

data:須要傳遞的數據,html5規範支持任意基本類型或可複製的對象,但部分瀏覽器只支持字符串,因此傳參時最好用JSON.stringify()序列化。
origin:協議+主機+端口號,也能夠設置爲"*",表示能夠傳遞給任意窗口,若是要指定和當前窗口同源的話設置爲"/"。

代碼示例:
http://www.a.com/a.html

&lt;iframe id="iframe" src="http://www.b.com/b.html" style="display:none;"&gt;&lt;/iframe&gt;
&lt;script&gt;       
    var iframe = document.getElementById('iframe');
    iframe.onload = function() {
        var data = {
            name: 'jianjian'
        };
        // 向http://www.b.com傳送跨域數據
        iframe.contentWindow.postMessage(JSON.stringify(data),'http://www.b.com');
    };

    // 接受http://www.b.com返回數據
    window.addEventListener('message', function(e) {
        alert('data from http://www.b.com---&gt; ' + e.data);
    }, false);
&lt;/script&gt;

http://www.b.com/b.html

&lt;script&gt;
    // 接收http://www.a.com/a.html的數據
    window.addEventListener('message', function(e) {
        alert('data from http://www.a.com/a.html---&gt; ' + e.data);

        var data = JSON.parse(e.data);
        if (data) {
            data.number = 16;

            // 處理後再發回http://www.a.com/a.html
            window.parent.postMessage(JSON.stringify(data), 'http://www.a.com');
        }
    }, false);
&lt;/script&gt;

4 document.domain

這種方式只適合主域名相同,但子域名不一樣的iframe跨域。
實現原理:兩個頁面都經過js強制設置document.domain爲基礎主域,就實現了同域。

使用方式:
http://www.a.com/a.html

&lt;iframe id="iframe" src="http://www.child.a.com/b.html" style="display:none;"&gt;&lt;/iframe&gt;
&lt;script&gt;      
      document.domain = 'a.com'; 
      
      var a = 'hello world';
   
 
&lt;/script&gt;

"http://www.child.a.com/b.html

&lt;script&gt;      
      document.domain = 'a.com'; 
      
      var b = window.parent.a;
   
      console.log(b);
&lt;/script&gt;

5 window.name

window.name 傳輸技術的基本原理:
當在瀏覽器中打開一個頁面,或者在頁面中添加一個iframe時即會建立一個對應的window對象,當頁面加載另外一個新的頁面時,window.name的屬性是不會變的。這樣就能夠利用在頁面動態添加一個iframe而後加載數據頁面,在數據頁面將須要的數據賦值給window.name。然而此時承載的iframe的parent頁面仍是不能直接訪問不在同一域下的iframe的那麼屬性,這時,只須要將iframe再加載一個與承載頁面同域的空白頁面,便可對window.name進行數據讀取。
經過iframe的src屬性由外域轉向本地域,跨域數據即由iframe的window.name從外域傳遞到本地域。這個就巧妙地繞過了瀏覽器的跨域訪問限制,但同時它又是安全操做。

具體實現:
http://www.a.com/a.html 主頁面
http://www.b.com/b.html 數據頁面
http://www.a.com/proxy.html 代理頁面

http://www.a.com/a.html代碼

&lt;script&gt;

 
function crosDomainGetData(url,callback){

    var state = 0;
    var iframe = document.createElement('iframe);
    iframe.src = url;
    
    iframe.onload = function(){
        if(state === 1){
           //代理頁面成功事後,讀取window.name
            var data = iframe.contentWindow.name;
            callback&amp;&amp;callback(data);
            
            //銷燬iframe
            iframe.contentWindow.document.write('');
            iframe.contentWindow.close();
            document.body.removeChild(iframe);          
        } else {
            //第一次加載數據頁面成功後,切換代理頁面
            state = 1;
            iframe.contentWindow.location = 'http://www.a.com/proxy.html';
        }
    }
    
    document.body.appendChild(iframe);


}

crosDomainGetData('http://www.b.com/b.html',function(data){
    alert(data);
})




&lt;/script&gt;

http://www.b.com/b.html代碼

window.name = '123'

http://www.a.com/proxy.html空白

6 nginx代理跨域

server{
    # 監聽8080端口
    listen 8080;
    # 域名是localhost
    server_name localhost;
    #凡是localhost:8080/api這個樣子的,都轉發到真正的服務端地址http://www.b.com:8080 
    location ^~ /api {
        proxy_pass http://www.b.com:8080;
    }    
}
配置以後就不須要前端作什麼修改了,通常咱們在先後端分離項目中開發階段會採用這種方式,但不是全部場景都能這樣作,例如後端接口是一個公共的API,好比一些公共服務獲取天氣什麼的。

7 WebSocket協議跨域

websoket協議自然支持跨域,你只須要學會如何使用它便可,關於websocket協議請看個人另一篇文章WebSocket網絡通訊協議

參考文章:
https://developer.mozilla.org...
https://segmentfault.com/a/11...

原文地址:https://segmentfault.com/a/1190000017312269

相關文章
相關標籤/搜索