九種 “姿勢(shì)” 讓你徹底解決跨域問(wèn)題


原文出自:https://www.pandashen.com


同源策略

同源策略/SOP(Same origin policy)是一種約定俘陷,由 Netscape 公司 1995 年引入瀏覽器零渐,它是瀏覽器最核心也最基本的安全功能句携,如果缺少了同源策略远寸,瀏覽器很容易受到 XSS、CSRF 等攻擊卧斟。所謂同源是指 "協(xié)議 + 域名 + 端口" 三者相同链烈,即便兩個(gè)不同的域名指向同一個(gè) ip 地址,也非同源驻债。


什么是跨域乳规?

當(dāng)協(xié)議、域名合呐、端口號(hào)暮的,有一個(gè)或多個(gè)不同時(shí),有希望可以訪問(wèn)并獲取數(shù)據(jù)的現(xiàn)象稱為跨域訪問(wèn)淌实,同源策略限制下 cookie冻辩、localStorage拆祈、dom恨闪、ajaxIndexDB 都是不支持跨域的放坏。

假設(shè) cookie 支持了跨域咙咽,http 協(xié)議無(wú)狀態(tài),當(dāng)用戶訪問(wèn)了一個(gè)銀行網(wǎng)站登錄后淤年,銀行網(wǎng)站的服務(wù)器給返回了一個(gè) sessionId钧敞,當(dāng)通過(guò)當(dāng)前瀏覽器再訪問(wèn)一個(gè)惡意網(wǎng)站,如果 cookie 支持跨域麸粮,惡意網(wǎng)站將獲取 sessionId 并訪問(wèn)銀行網(wǎng)站溉苛,出現(xiàn)安全性問(wèn)題;IndexDB豹休、localStorage 等數(shù)據(jù)存儲(chǔ)在不同域的頁(yè)面切換時(shí)是獲取不到的炊昆;假設(shè) dom 元素可以跨域,在自己的頁(yè)面寫(xiě)入一個(gè) iframe 內(nèi)部嵌入的地址是 <a href="javascript:;">www.baidu.com</a>威根,當(dāng)在百度頁(yè)面登錄賬號(hào)密碼時(shí)就可以在自己的頁(yè)面獲取百度的數(shù)據(jù)信息凤巨,這顯然是不合理的。

這就是為什么 cookie洛搀、localStorage敢茁、domajaxIndexDB 會(huì)受到同源策略會(huì)限制亮蒋,下面還有一點(diǎn)對(duì)跨域理解的誤區(qū):

誤區(qū):同源策略限制下趁怔,訪問(wèn)不到后臺(tái)服務(wù)器的數(shù)據(jù)蚤吹,或訪問(wèn)到后臺(tái)服務(wù)器的數(shù)據(jù)后沒(méi)有返回二驰;
正確:同源策略限制下棘捣,可以訪問(wèn)到后臺(tái)服務(wù)器的數(shù)據(jù)评疗,后臺(tái)服務(wù)器會(huì)正常返回?cái)?shù)據(jù),而被瀏覽器給攔截了。


實(shí)現(xiàn)跨域的方式

一、使用 jsonp 跨域

使用場(chǎng)景:當(dāng)自己的項(xiàng)目前端資源和后端部署在不同的服務(wù)器地址上逊笆,或者其他的公司需要訪問(wèn)自己對(duì)外公開(kāi)的接口乃戈,需要實(shí)現(xiàn)跨域獲取數(shù)據(jù)习贫,如百度搜索。

// 封裝 jsonp 跨域請(qǐng)求的方法
function jsonp({ url, params, cb }) {
    return new Promise((resolve, reject) => {
        // 創(chuàng)建一個(gè) script 標(biāo)簽幫助我們發(fā)送請(qǐng)求
        let script = document.createElement("script");
        let arr = [];
        params = { ...params, cb };

        // 循環(huán)構(gòu)建鍵值對(duì)形式的參數(shù)
        for (let key in params) {
            arr.push(`${key}=${params[key]}`);
        }

        // 創(chuàng)建全局函數(shù)
        window[cb] = function(data) {
            resolve(data);
            // 在跨域拿到數(shù)據(jù)以后將 script 標(biāo)簽銷毀
            document.body.removeChild(script);
        };

        // 拼接發(fā)送請(qǐng)求的參數(shù)并賦值到 src 屬性
        script.src = `${url}?${arr.join("&")}`;
        document.body.appendChild(script);
    });
}

// 調(diào)用方法跨域請(qǐng)求百度搜索的接口
json({
    url: "https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su",
    params: {
        wd: "jsonp"
    },
    cb: "show"
}).then(data => {
    // 打印請(qǐng)求回的數(shù)據(jù)
    console.log(data);
});

缺點(diǎn):

  • 只能發(fā)送 get 請(qǐng)求 不支持 post、put父款、delete溢谤;
  • 不安全憨攒,容易引發(fā) xss 攻擊,別人在返回的結(jié)果中返回了下面代碼。
`let script = document.createElement('script');
script.src = "http://192.168.0.57:8080/xss.js";
document.body.appendChild(script);`;

會(huì)把別人的腳本引入到自己的頁(yè)面中執(zhí)行杏瞻,如:彈窗所刀、廣告等,甚至更危險(xiǎn)的腳本程序捞挥。


二浮创、使用 CORS 跨域

跨源資源共享/CORS(Cross-Origin Resource Sharing)是 W3C 的一個(gè)工作草案,定義了在必須訪問(wèn)跨源資源時(shí)砌函,瀏覽器與服務(wù)器應(yīng)該如何溝通斩披。CORS 背后的基本思想,就是使用自定義的 HTTP 頭部讓瀏覽器與服務(wù)器進(jìn)行溝通讹俊,從而決定請(qǐng)求或響應(yīng)是應(yīng)該成功雏掠,還是應(yīng)該失敗。

使用場(chǎng)景:多用于開(kāi)發(fā)時(shí)劣像,前端與后臺(tái)在不同的 ip 地址下進(jìn)行數(shù)據(jù)訪問(wèn)乡话。

現(xiàn)在啟動(dòng)兩個(gè)端口號(hào)不同的服務(wù)器考余,創(chuàng)建跨域條件浓冒,服務(wù)器(NodeJS)代碼如下:

// 服務(wù)器1
const express = require(express);
let app = express();
app.use(express.static(__dirname));
app.listen(3000);

// 服務(wù)器2
const express = require("express");
let app = express();
app.get("/getDate", function(req, res) {
    res.end("I love you");
});
app.use(express.static(__dirname));
app.listen(4000);

由于我們的 NodeJS 服務(wù)器使用 express 框架贮竟,在我們的項(xiàng)目根目錄下的命令行中輸入下面代碼進(jìn)行安裝:

npm install express --save

通過(guò)訪問(wèn) <a href="javascript:;">http://localhost:3000/index.html</a> 獲取 index.html 文件并執(zhí)行其中的 Ajax 請(qǐng)求 <a href="javascript:;">http://localhost:4000/getDate</a> 接口去獲取數(shù)據(jù)溅蛉,index.html 文件內(nèi)容如下:

<!-- 文件:index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>CORS 跨域</title>
</head>
<body>
    <script>
        let xhr = new XMLHttpRequest();

        // 正常 cookie 是不允許跨域的
        document.cookie = 'name=hello';

        // cookie 想要實(shí)現(xiàn)跨域必須攜帶憑證
        xhr.withCredentials = true;

        // xhr.open('GET', 'http://localhost:4000/getDate', true);
        xhr.open('PUT', 'http://localhost:4000/getDate', true);

        // 設(shè)置名為 name 的自定義請(qǐng)求頭
        xhr.setRequestHeader('name', 'hello');

        xhr.onreadystatechange = function () {
            if(xhr.readyState === 4) {
                if(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
                    // 打印返回的數(shù)據(jù)
                    console.log(xhr.response);

                    // 打印后臺(tái)設(shè)置的自定義頭信息
                    console.log(xhr.getResponseHeader('name'));
                }
            }
        }
        xhr.send();
    </script>
</body>
</html>

上面 index.html 代碼中發(fā)送請(qǐng)求訪問(wèn)不在同源的服務(wù)器 2睛琳,此時(shí)會(huì)在控制臺(tái)給出錯(cuò)誤信息盹愚,告訴我們?nèi)鄙倭四男╉憫?yīng)頭飞醉,我們對(duì)應(yīng)報(bào)錯(cuò)信息去修改訪問(wèn)的服務(wù)器 2 的代碼奈懒,添加對(duì)應(yīng)的響應(yīng)頭芍躏,實(shí)現(xiàn) CORS 跨域邪乍。

// 服務(wù)器2
const express = require("express");
let app = express();

// 允許訪問(wèn)域的白名單
let whiteList = ["http://localhost:3000"];

app.use(function(req, res, next) {
    let origin = req.header.origin;
    if (whiteList.includes(origin)) {
        // 設(shè)置那個(gè)源可以訪問(wèn)我,參數(shù)為 * 時(shí),允許任何人訪問(wèn)庇楞,但是不可以和 cookie 憑證的響應(yīng)頭共同使用
        res.setHeader("Access-Control-Allow-Origin", origin);
        // 想要獲取 ajax 的頭信息榜配,需設(shè)置響應(yīng)頭
        res.setHeader("Access-Control-Allow-Headers", "name");
        // 處理復(fù)雜請(qǐng)求的頭
        res.setHeader("Access-Control-Allow-Methods", "PUT");
        // 允許發(fā)送 cookie 憑證的響應(yīng)頭
        res.setHeader("Access-Control-Allow-Credentials", true);
        // 允許前端獲取哪個(gè)頭信息
        res.setHeader("Access-Control-Expose-Headers", "name");
        // 處理 OPTIONS 預(yù)檢的存活時(shí)間,單位 s
        res.setHeader("Access-Control-Max-Age", 5);
        // 發(fā)送 PUT 請(qǐng)求會(huì)做一個(gè)試探性的請(qǐng)求 OPTIONS吕晌,其實(shí)是請(qǐng)求了兩次蛋褥,當(dāng)接收的請(qǐng)求為 OPTIONS 時(shí)不做任何處理
        if (req.method === "OPTIONS") {
            res.end();
        }
    }
    next();
});

app.put("/getDate", function(req, res) {
    // res.setHeader('name', 'nihao'); // 設(shè)置自定義響應(yīng)頭信息
    res.end("I love you");
});

app.get("/getDate", function(req, res) {
    res.end("I love you");
});

app.use(express.static(__dirname));
app.listen(4000);


三、使用 postMessage 實(shí)現(xiàn)跨域

postMessage 是 H5 的新 API睛驳,跨文檔消息傳送(cross-document messaging)烙心,有時(shí)候簡(jiǎn)稱為 XMD,指的是在來(lái)自不同域的頁(yè)面間傳遞消息乏沸。

調(diào)用方式:window.postMessage(message, targetOrigin)

  • message:發(fā)送的數(shù)據(jù)
  • targetOrigin:發(fā)送的窗口的域

在對(duì)應(yīng)的頁(yè)面中用 message 事件接收淫茵,事件對(duì)象中有 dataorigin蹬跃、source 三個(gè)重要信息

  • data:接收到的數(shù)據(jù)
  • origin:接收到數(shù)據(jù)源的域(數(shù)據(jù)來(lái)自哪個(gè)域)
  • source:接收到數(shù)據(jù)源的窗口對(duì)象(數(shù)據(jù)來(lái)自哪個(gè)窗口對(duì)象)

使用場(chǎng)景:不是使用 Ajax 的數(shù)據(jù)通信痘昌,更多是在兩個(gè)頁(yè)面之間的通信,在 A 頁(yè)面中引入 B 頁(yè)面炬转,在 AB 兩個(gè)頁(yè)面之間通信算灸。

與上面 CORS 類似扼劈,我們要?jiǎng)?chuàng)建跨域場(chǎng)景,搭建兩個(gè)端口號(hào)不同的 Nodejs 服務(wù)器菲驴,后面相同方式就不多贅述了荐吵。

// 服務(wù)器1
const express = require(express);
let app = express();
app.use(express.static(__dirname));
app.listen(3000);

// 服務(wù)器2
const express = require(express);
let app = express();
app.use(express.static(__dirname));
app.listen(4000);

通過(guò)訪問(wèn) <a href="javascript:;">http://localhost:3000/a.html</a>,在 a.html 中使用 iframe 標(biāo)簽引入 <a href="javascript:;">http://localhost:4000/b.html</a>赊瞬,在兩個(gè)窗口間傳遞數(shù)據(jù)先煎。

<!-- 文件:a.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>頁(yè)面 A</title>
</head>
<body>
    <iframe src="http://localhost:4000/b.html" id="frame" onload="load()"></iframe>
    <script>
        function load() {
            let frame = document.getElementById('frame');
            frame.contentWindow.postMessage('I love you', 'http://localhost:4000');
            window.onmessage = function (e) {
                console.log(e.data);
            }
        }
    </script>
</body>
</html>
<!-- 文件:b.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>頁(yè)面 B</title>
</head>
<body>
    <script>
        window.onmessage = function (e) {
            // 打印來(lái)自頁(yè)面 A 的消息
            console.log(e.data);
            // 給頁(yè)面 A 發(fā)送回執(zhí)
            e.source.postMessage('I love you, too', e.origin);
        }
    </script>
</body>
</html>


四、使用 window.name 實(shí)現(xiàn)跨域

同樣是頁(yè)面之間的通信巧涧,需要借助 iframe 標(biāo)簽薯蝎,A 頁(yè)面和 B 頁(yè)面是同域的 <a href="javascript:;">http://localhost:3000</a>,C 頁(yè)面在獨(dú)立的域 <a href="javascript:;">http://localhost:4000</a>谤绳。

// 服務(wù)器1
const express = require(express);
let app = express();
app.use(express.static(__dirname));
app.listen(3000);

// 服務(wù)器2
const express = require(express);
let app = express();
app.use(express.static(__dirname));
app.listen(4000);

實(shí)現(xiàn)思路:在 A 頁(yè)面中將 iframesrc 指向 C 頁(yè)面占锯,在 C 頁(yè)面中將屬性值存入 window.name 中,再把 iframesrc 換成同域的 B 頁(yè)面缩筛,在當(dāng)前的 iframewindow 對(duì)象中取出 name 的值消略,訪問(wèn) <a href="javascript:;">http://localhost:3000/a.html</a>。

<!-- 文件:a.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>頁(yè)面 A</title>
</head>
<body>
    <iframe src="http://localhost:4000/c.html" id="frame" onload="load()"></iframe>
    <script>
        // 增加一個(gè)標(biāo)識(shí)瞎抛,第一次觸發(fā) load 時(shí)更改地址艺演,更改后再次觸發(fā)直接取值
        let isFirst = true;
        function load() {
            let frame = document.getElementById('frame');
            if(isFirst) {
                frame.src = 'http://localhost:3000/b.html';
                isFirst = false;
            } else {
                console.log(frame.contentWindow.name);
            }
        }
    </script>
</body>
</html>
<!-- 文件:c.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>頁(yè)面 C</title>
</head>
<body>
    <script>
        window.name = 'I love you';
    </script>
</body>
</html>


五、使用 location.hash 實(shí)現(xiàn)跨域

window.name 跨域的情況相同,是不同域的頁(yè)面間的參數(shù)傳遞胎撤,需要借助 iframe 標(biāo)簽晓殊,A 頁(yè)面和 B 頁(yè)面是同域的 <a href="javascript:;">http://localhost:3000</a>,C 頁(yè)面是獨(dú)立的域 <a href="javascript:;">http://localhost:4000</a>哩照。

// 服務(wù)器1
const express = require(express);
let app = express();
app.use(express.static(__dirname));
app.listen(3000);

// 服務(wù)器2
const express = require(express);
let app = express();
app.use(express.static(__dirname));
app.listen(4000);

實(shí)現(xiàn)思路:A 頁(yè)面通過(guò) iframe 引入 C 頁(yè)面挺物,并給 C 頁(yè)面?zhèn)饕粋€(gè) hash 值,C 頁(yè)面收到 hash 值后創(chuàng)建 iframe 引入 B 頁(yè)面飘弧,把 hash 值傳給 B 頁(yè)面识藤,B 頁(yè)面將自己的 hash 值放在 A 頁(yè)面的 hash 值中,訪問(wèn) <a href="javascript:;">http://localhost:3000/a.html</a>次伶。

<!-- 文件:a.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>頁(yè)面 A</title>
</head>
<body>
    <iframe src="http://localhost:4000/c.html#Iloveyou" id="frame"></iframe>
    <script>
        // 使用 hashchange 事件接收來(lái)自 B 頁(yè)面設(shè)置給 A 頁(yè)面的 hash 值
        window.onhashchange = function () {
            console.log(location.hash);
        }
    </script>
</body>
</html>
<!-- 文件:c.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>頁(yè)面 C</title>
</head>
<body>
    <script>
        // 打印 A 頁(yè)面引入 C 頁(yè)面設(shè)置的 hash 值
        console.log(location.hash);
        let iframe = document.createElement('iframe');
        iframe.src = 'http://localhost:3000/b.html#Iloveyoutoo';
        document.body.appendChild(iframe);
    </script>
</body>
</html>
<!-- 文件:b.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>頁(yè)面 B</title>
</head>
<body>
    <script>
        // 將 C 頁(yè)面引入 B 頁(yè)面設(shè)置的 hash 值設(shè)置給 A頁(yè)面
        window.parent.parent.location.hash = location.hash;
    </script>
</body>
</html>


六痴昧、使用 document.domain 實(shí)現(xiàn)跨域

使用場(chǎng)景:不是萬(wàn)能的跨域方式,大多使用于同一公司不同產(chǎn)品間獲取數(shù)據(jù)冠王,必須是一級(jí)域名和二級(jí)域名的關(guān)系赶撰,如 <a href="javascript:;">www.baidu.com</a> 與 <a href="javascript:;">video.baidu.com</a> 之間。

const express = require("express");
let app = express();

app.use(express.static(__dirname));
app.listen(3000);

想要模擬使用 document.domain 跨域的場(chǎng)景需要做些小小的準(zhǔn)備柱彻,到 <a href="javascript:;">C:\Windows\System32\drivers\etc</a> 該路徑下找到 hosts 文件豪娜,在最下面創(chuàng)建一個(gè)一級(jí)域名和一個(gè)二級(jí)域名。

127.0.0.1 ???????? <a href="javascript:;">www.domainacross.com</a>
127.0.0.1 ???????? <a href="javascript:;">sub.domainacross.com</a>

命名是隨意的哟楷,只要是符合一級(jí)域名與 二級(jí)域名的關(guān)系即可瘤载,然后訪問(wèn) <a href="javascript:;">http://www.domainacross.com:3000/a.html</a>。

<!-- 文件:a.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>頁(yè)面 A</title>
</head>
<body>
    <p>我是頁(yè)面 A 的內(nèi)容</p>
    <iframe src="http://sucess.domainacross.com:3000/b.html" onload="load()" id="frame"></iframe>
    <script>
        document.domain = 'domainacross.com';
        function load() {
            console.log(frame.contentWindow.message);
        }
    </script>
</body>
</html>
<!-- 文件:b.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>頁(yè)面 B</title>
</head>
<body>
    <p>我是 B 頁(yè)面的內(nèi)容</p>
    <script>
        document.domain = 'domainacross.com';
        var message = 'Hello A';
    </script>
</body>
</html>


七卖擅、使用 WebSocket 實(shí)現(xiàn)跨域

WebSocket 沒(méi)有跨域限制鸣奔,高級(jí) API(不兼容),想要兼容低版本瀏覽器惩阶,可以使用 socket.io 的庫(kù)挎狸,WebSocket 與 HTTP 內(nèi)部都是基于 TCP 協(xié)議,區(qū)別在于 HTTP 是單向的(單雙工)断楷,WebSocket 是雙向的(全雙工)锨匆,協(xié)議是 ws://wss:// 對(duì)應(yīng) http://https://,因?yàn)闆](méi)有跨域限制冬筒,所以使用 file:// 協(xié)議也可以進(jìn)行通信统刮。

由于我們?cè)?NodeJS 服務(wù)中使用了 WebSocket,所以需要安裝對(duì)應(yīng)的依賴:

npm install ws --save

<!-- 文件:index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>頁(yè)面</title>
</head>
<body>
    <script>
        // 創(chuàng)建 webSocket
        let socket = new WebSocket('ws://localhost:3000');
        // 連接上觸發(fā)
        socket.onopen = function () {
            socket.send('I love you');
        }
        // 收到消息觸發(fā)
        socket.onmessage = function (e) {
            // 打印收到的數(shù)據(jù)
            console.log(e.data); // I love you, too
        }
    </script>
</body>
</html>
const express = require("express");
let app = express();

// 引入 webSocket
const WebSocket = require("ws");
// 創(chuàng)建連接账千,端口號(hào)與前端相對(duì)應(yīng)
let wss = new WebSocket.Server({ port: 3000 });

// 監(jiān)聽(tīng)連接
wss.on("connection", function(ws) {
    // 監(jiān)聽(tīng)消息
    ws.on("message", function(data) {
        // 打印消息
        console.log(data); // I love you
        // 發(fā)送消息
        ws.send("I love you, too");
    });
});


八侥蒙、使用 nginx 實(shí)現(xiàn)跨域

nginx 本身就是一個(gè)服務(wù)器,因此我們需要去 nginx 官網(wǎng)下載服務(wù)環(huán)境 <a>http://nginx.org/en/download.html</a>匀奏。

  • 下載后解壓到一個(gè)文件夾中
  • 雙擊 nginx.exe 啟動(dòng)(此時(shí)可以通過(guò) <a href="javascript:;">http://localhost</a> 訪問(wèn) nginx 服務(wù))
  • 在目錄新建 json 文件夾
  • 進(jìn)入 json 文件夾新建 data.json 文件并寫(xiě)入內(nèi)容
  • 回到 nginx 根目錄進(jìn)入 conf 文件夾
  • 使用編輯器打開(kāi) nginx.conf 進(jìn)行配置

data.json 文件:

{
    "name": "nginx"
}

nginx.conf 文件:

server {
    .
    .
    .
    location ~.*\.json {
        root json;
        add_header "Access-Control-Allow-Origin" "*";
    }
    .
    .
    .
}

含義:

  • ~.*\.json:代表忽略大小寫(xiě)鞭衩,后綴名為 json 的文件;
  • root json:代表 json 文件夾;
  • add_header:代表加入跨域的響應(yīng)頭及允許訪問(wèn)的域论衍,* 為允許任何訪問(wèn)瑞佩。

nginx 根目錄啟動(dòng) cmd 命令行(windows 系統(tǒng)必須使用 cmd 命令行)執(zhí)行下面代碼重啟 nginx

nginx -s reload

不跨域訪問(wèn):<a href="javascript:;">http://localhost/data.json</a>

跨域訪問(wèn)時(shí)需要?jiǎng)?chuàng)建跨域條件代碼如下:

// 服務(wù)器
const express = require("express");
let app = express();

app.use(express.static(__dirname));
app.listen(3000);

跨域訪問(wèn):<a href="javascript:;">http://localhost:3000/index.html</a>

<!-- 文件:index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>nginx跨域</title>
</head>
<body>
    <script>
        let xhr = new XMLHttpRequest();
        xhr.open('GET', 'http://localhost/data.json', true);
        xhr.onreadystatechange = function () {
            if(xhr.readyState === 4) {
                if(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
                    console.log(xhr.response);
                }
            }
        }
        xhr.send();
    </script>
</body>
</html>


九坯台、使用 http-proxy-middleware 實(shí)現(xiàn)跨域

NodeJS 中間件 http-proxy-middleware 實(shí)現(xiàn)跨域代理炬丸,原理大致與 nginx 相同,都是通過(guò)啟一個(gè)代理服務(wù)器蜒蕾,實(shí)現(xiàn)數(shù)據(jù)的轉(zhuǎn)發(fā)稠炬,也可以通過(guò)設(shè)置 cookieDomainRewrite 參數(shù)修改響應(yīng)頭中 cookie 中的域名,實(shí)現(xiàn)當(dāng)前域的 cookie 寫(xiě)入咪啡,方便接口登錄認(rèn)證首启。

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

<!-- 文件:index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>proxy 跨域</title>
</head>
<body>
    <script>
        var xhr = new XMLHttpRequest();

        // 前端開(kāi)關(guān):瀏覽器是否讀寫(xiě) cookie
        xhr.withCredentials = true;

        // 訪問(wèn) http-proxy-middleware 代理服務(wù)器
        xhr.open('get', 'http://www.proxy1.com:3000/login?user=admin', true);
        xhr.send();
    </script>
</body>
</html>

中間代理服務(wù)中使用了 http-proxy-middleware 中間件撤摸,因此需要提前下載:

npm install http-proxy-middleware --save-dev

// 中間代理服務(wù)器
const express = require("express");
let proxy = require("http-proxy-middleware");
let app = express();

app.use(
    "/",
    proxy({
        // 代理跨域目標(biāo)接口
        target: "http://www.proxy2.com:8080",
        changeOrigin: true,

        // 修改響應(yīng)頭信息毅桃,實(shí)現(xiàn)跨域并允許帶 cookie
        onProxyRes: function(proxyRes, req, res) {
            res.header("Access-Control-Allow-Origin", "http://www.proxy1.com");
            res.header("Access-Control-Allow-Credentials", "true");
        },

        // 修改響應(yīng)信息中的 cookie 域名
        cookieDomainRewrite: "www.proxy1.com" // 可以為 false,表示不修改
    })
);

app.listen(3000);
// 服務(wù)器
const http = require("http");
const qs = require("querystring");

const server = http.createServer();

server.on("request", function(req, res) {
    let params = qs.parse(req.url.substring(2));

    // 向前臺(tái)寫(xiě) cookie
    res.writeHead(200, {
        "Set-Cookie": "l=a123456;Path=/;Domain=www.proxy2.com;HttpOnly" // HttpOnly:腳本無(wú)法讀取
    });

    res.write(JSON.stringify(params));
    res.end();
});

server.listen("8080");

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

利用 node + webpack + webpack-dev-server 代理接口跨域钥飞。在開(kāi)發(fā)環(huán)境下,由于 Vue 渲染服務(wù)和接口代理服務(wù)都是 webpack-dev-server衫嵌,所以頁(yè)面與代理接口之間不再跨域读宙,無(wú)須設(shè)置 Headers 跨域信息了。

// 導(dǎo)出服務(wù)器配置
module.exports = {
    entry: {},
    module: {},
    ...
    devServer: {
        historyApiFallback: true,
        proxy: [{
            context: '/login',
            target: 'http://www.proxy2.com:8080',  // 代理跨域目標(biāo)接口
            changeOrigin: true,
            secure: false,  // 當(dāng)代理某些 https 服務(wù)報(bào)錯(cuò)時(shí)用
            cookieDomainRewrite: 'www.domain1.com'  // 可以為 false渐扮,表示不修改
        }],
        noInfo: true
    }
}


本篇文章在于幫助我們理解跨域,以及不同跨域方式的基本原理掖棉,在公司的項(xiàng)目比較多墓律,多個(gè)域使用同一個(gè)服務(wù)器或者數(shù)據(jù),以及在開(kāi)發(fā)環(huán)境時(shí)幔亥,跨域的情況基本無(wú)法避免耻讽,一般會(huì)有各種各樣形式的跨域解決方案,但其根本原理基本都在上面的跨域方式當(dāng)中方式帕棉,我們可以根據(jù)開(kāi)發(fā)場(chǎng)景不同针肥,選擇最合適的跨域解決方案。


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末香伴,一起剝皮案震驚了整個(gè)濱河市慰枕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌即纲,老刑警劉巖具帮,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蜂厅,警方通過(guò)查閱死者的電腦和手機(jī)匪凡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)掘猿,“玉大人病游,你說(shuō)我怎么就攤上這事〕硗ǎ” “怎么了衬衬?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)采记。 經(jīng)常有香客問(wèn)我佣耐,道長(zhǎng),這世上最難降的妖魔是什么唧龄? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任兼砖,我火速辦了婚禮,結(jié)果婚禮上既棺,老公的妹妹穿的比我還像新娘讽挟。我一直安慰自己,他們只是感情好丸冕,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布耽梅。 她就那樣靜靜地躺著,像睡著了一般胖烛。 火紅的嫁衣襯著肌膚如雪眼姐。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天佩番,我揣著相機(jī)與錄音众旗,去河邊找鬼。 笑死趟畏,一個(gè)胖子當(dāng)著我的面吹牛贡歧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播赋秀,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼利朵,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了猎莲?” 一聲冷哼從身側(cè)響起绍弟,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎著洼,沒(méi)想到半個(gè)月后晌柬,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體姥份,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年年碘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了澈歉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡屿衅,死狀恐怖埃难,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情涤久,我是刑警寧澤涡尘,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站响迂,受9級(jí)特大地震影響考抄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蔗彤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一川梅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧然遏,春花似錦贫途、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至秧倾,卻和暖如春怨酝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背那先。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工农猬, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人胃榕。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓盛险,卻偏偏與公主長(zhǎng)得像瞄摊,于是被迫代替她去往敵國(guó)和親勋又。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 什么是跨域 跨域换帜,是指瀏覽器不能執(zhí)行其他網(wǎng)站的腳本楔壤。它是由瀏覽器的同源策略造成的,是瀏覽器對(duì)JavaScript實(shí)...
    HeroXin閱讀 831評(píng)論 0 4
  • 什么是跨域 跨域惯驼,是指瀏覽器不能執(zhí)行其他網(wǎng)站的腳本蹲嚣。它是由瀏覽器的同源策略造成的递瑰,是瀏覽器對(duì)JavaScript實(shí)...
    他方l閱讀 1,059評(píng)論 0 2
  • 這個(gè)世界就是都是一樣的 沒(méi)有什么是不一樣的 差不多先生
    泰_3fa6閱讀 140評(píng)論 1 1
  • 幾年前的一次拓展培訓(xùn)上,老師帶我們作了一個(gè)測(cè)試隙畜,把一天用在工作抖部、鍛煉、家人议惰、生活的時(shí)間詳細(xì)寫(xiě)出來(lái)慎颗,做成雷達(dá)圖,看我...
    西蘇Sisu閱讀 223評(píng)論 0 1
  • 這是一段記憶言询。 那個(gè)飄雪的早晨俯萎,“好久沒(méi)有你的信,好久沒(méi)有人陪我談心”手機(jī)鈴聲叫我起床运杭,可已經(jīng)養(yǎng)成的習(xí)慣就是...
    澤L不愛(ài)閱讀 373評(píng)論 0 1