一講就懂!常見面試題之一---跨域常見解決辦法

爲什麼需要跨域,可能我們大家都懂,就是瀏覽器會有個同源策略(協議、域名、端口都必須相同),其中任何一個不同,都會當做是不同的域,也就是說協議、域名、端口有一個不同,就會認作是跨域,這時就需要通過跨域的解決方案。

常見的跨域場景

摘自https://segmentfault.com/a/1190000011145364

URL                                      說明                    是否允許通信
http://www.domain.com/a.js
http://www.domain.com/b.js         同一域名,不同文件或路徑           允許
http://www.domain.com/lab/c.js

http://www.domain.com:8000/a.js
http://www.domain.com/b.js         同一域名,不同端口                不允許
 
http://www.domain.com/a.js
https://www.domain.com/b.js        同一域名,不同協議                不允許
 
http://www.domain.com/a.js
http://192.168.4.12/b.js           域名和域名對應相同ip              不允許
 
http://www.domain.com/a.js
http://x.domain.com/b.js           主域相同,子域不同                不允許
http://domain.com/c.js
 
http://www.domain1.com/a.js
http://www.domain2.com/b.js        不同域名                         不允許

以下是跨域的解決辦法

  • 1.通過jsonp解決跨域

  • 2.postMessage跨域

  • 3.資源共享跨域(CORS)

  • 4.nginx代理跨域

  • 5.nodejs中間件代理跨域

    • (1)非vue框架的跨域(2次跨域)
    • (2)vue框架跨域
  • 7.webscoket協議跨域

1.通過jsonp解決跨域

JSONP 的原理很簡單,就是利用 < script> 標籤沒有跨域限制的漏洞。通過 < script>
標籤指向一個需要訪問的地址並提供一個回調函數來接收數據當需要通訊時。
eg:

<script src="http://domain/api?param1=a&param2=b&callback=jsonp"></script>
<script>
    function jsonp(data) {
    	console.log(data)
	}
</script>

JSONP 使用簡單且兼容性不錯,但是隻限於 get 請求。(缺點)

通用的辦法如下:

<script>
    var script = document.createElement('script');
    script.type = 'text/javascript';

    // 傳參一個回調函數名給後端,方便後端返回時執行這個在前端定義的回調函數
    script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
    document.head.appendChild(script);

    // 回調執行函數
    function handleCallback(value) {
        console.log(value)
    }
 </script>

vue.js實現

this.$http.jsonp('http://www.domain2.com:8080/login', {
    params: {},
    jsonp: 'handleCallback'
}).then((res) => {
    console.log(res); 
})

後端node.js代碼實例

var querystring = require('querystring');
var http = require('http');
var server = http.createServer();

server.on('request', function(req, res) {
    var params = qs.parse(req.url.split('?')[1]);
    var fn = params.callback;

    // jsonp返回設置
    res.writeHead(200, { 'Content-Type': 'text/javascript' });
    res.write(fn + '(' + JSON.stringify(params) + ')');

    res.end();
});

server.listen('8080');
console.log('Server is running at port 8080...');

2.postMessage跨域

這是由H5提出來的的API,IE8以上支持這個功能。

window.postMessage() 方法可以安全地實現跨源通信。
window.postMessage() 方法被調用時,會在所有頁面腳本執行完畢之後,向目標窗口派發一個 MessageEvent 消息。

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

例子:
1.)a.html:(http://www.domain1.com/a.html)

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

    // 接受domain2返回數據
    window.addEventListener('message', function(e) {
        alert('data from domain2 ---> ' + e.data);
    }, false);
</script>

2.)b.html:(http://www.domain2.com/b.html)

<script>
    // 接收domain1的數據
    window.addEventListener('message', function(e) {
        alert('data from domain1 ---> ' + e.data);

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

            // 處理後再發回domain1
            window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');
        }
    }, false);
</script>

3.資源共享跨域(CORS)

普通跨域請求:只服務端設置Access-Control-Allow-Origin即可,前端無須設置,若要帶cookie請求:前後端都需要設置。

目前,所有瀏覽器都支持該功能(IE8+:IE8/9需要使用XDomainRequest對象來支持CORS)),CORS也已經成爲主流的跨域解決方案。

但不能實現修改cookie中domain信息,不方便當前域cookie寫入,即不能實現登錄認證。

4.nginx代理跨域

實現思路:通過nginx配置一個代理服務器(域名與domain1相同,端口不同)做跳板機,反向代理訪問domain2接口,並且可以順便修改cookie中domain信息,方便當前域cookie寫入,實現跨域登錄。
nginx具體配置:

#proxy服務器
server {
    listen       81;
    server_name  www.domain1.com;

    location / {
        proxy_pass   http://www.domain2.com:8080;  #反向代理
        proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie裏域名
        index  index.html index.htm;

        # 當用webpack-dev-server等中間件代理接口訪問nignx時,此時無瀏覽器參與,故沒有同源限制,下面的跨域配置可不啓用
        add_header Access-Control-Allow-Origin http://www.domain1.com;  #當前端只跨域不帶cookie時,可爲*
        add_header Access-Control-Allow-Credentials true;
    }
}

5.nodejs中間件代理跨域

node中間件實現跨域代理,原理大致與nginx相同,都是通過啓一個代理服務器,實現數據的轉發,也可以通過設置cookieDomainRewrite參數修改響應頭中cookie中域名,實現當前域的cookie寫入,方便接口登錄認證。

1、 非vue框架的跨域(2次跨域)

利用node + express + http-proxy-middleware搭建一個proxy服務器。
中間件服務器:

var express = require('express');
var proxy = require('http-proxy-middleware');
var app = express();

app.use('/', proxy({
    // 代理跨域目標接口
    target: 'http://www.domain2.com:8080',
    changeOrigin: true,

    // 修改響應頭信息,實現跨域並允許帶cookie
    onProxyRes: function(proxyRes, req, res) {
        res.header('Access-Control-Allow-Origin', 'http://www.domain1.com');
        res.header('Access-Control-Allow-Credentials', 'true');
    },

    // 修改響應信息中的cookie域名
    cookieDomainRewrite: 'www.domain1.com'  // 可以爲false,表示不修改
}));

app.listen(3000);
console.log('Proxy server is listen at port 3000...');
2、 vue框架跨域

利用node + webpack + webpack-dev-server代理接口跨域。在開發環境下,由於vue渲染服務和接口代理服務都是webpack-dev-server同一個,所以頁面與代理接口之間不再跨域,無須設置headers跨域信息了。

webpack.config.js部分配置:
module.exports = {
    entry: {},
    module: {},
    ...
    devServer: {
        historyApiFallback: true,
        proxy: [{
            context: '/sbcweb',
            target: 'http://www.domian2.com:8080',  // 代理跨域目標接口
            changeOrigin: true,
            secure: false,  // 當代理某些https服務報錯時用
            cookieDomainRewrite: 'www.domain1.com'  
            // 可以爲false,表示不修改
            // 填寫域名錶示可以更改cookie的域名值爲此域名,便可以導入cookie到網站中。
        }],
        noInfo: true
    }
}

6.webscoket協議跨域

WebSocket protocol是HTML5一種新的協議。它實現了瀏覽器與服務器全雙工通信,同時允許跨域通訊,是server push技術的一種很好的實現。
原生WebSocket API使用起來不太方便,我們使用Socket.io,它很好地封裝了webSocket接口,提供了更簡單、靈活的接口,也對不支持webSocket的瀏覽器提供了向下兼容。
WebSocket不會專門用來做跨域,而是作爲消息推送或者聊天等.

結尾

還有document.domain+iframe跨域,location.hash+iframe跨域和window.name+iframe跨域,我就不深入講述,有興趣可以自己查閱資料看看。

這些方法除了nodejs用得都不是特別多,個人認爲!

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