1. 程式人生 > >專案實戰之跨域處理~一文搞定所有跨域需求

專案實戰之跨域處理~一文搞定所有跨域需求

### 什麼是跨域? 跨域,是指瀏覽器不能執行其他網站的指令碼。它是由瀏覽器的同源策略造成的,是瀏覽器對JavaScript實施的安全限制。 ### 什麼是同源策略? 同源策略(Same origin policy)是一種約定,它是瀏覽器最核心也最基本的安全功能,它是由Netscape提出的一個著名的安全策略。 同源策略是瀏覽器的行為,是為了保護本地資料不被JavaScript程式碼獲取回來的資料汙染,因此攔截的是客戶端發出的請求回來的資料接收,即請求傳送了,伺服器響應了,但是無法被瀏覽器接收。 其主要限制以下幾個方面: * Cookie 、LocalStorage 和 IndexDB無法讀取 * 無法獲取或操作另一個資源的DOM * AJAX請求不能傳送 那麼什麼是同源呢?所謂的同源,就是指兩個頁面具有相同的協議,主機(也常說域名),埠,三個要素缺一不可。這樣可能不是很好理解,下面通過表格對比幫助大家理解: urlA | urlB | 說明 | 是否允許通訊 | ---|---|---|--- http://www.monkey.com/js/a.js | http://www.monkey.com/js/b.js | 協議、域名、埠、路徑都相同,檔案不同 | 允許 | http://www.monkey.com/a.js | http://www.monkey.com/js/b.js | 協議、域名、埠相同,路徑、檔案不同 | 允許 | http://www.monkey.com/js/a.js | http://www.monkey.com:3000/js/b.js | 協議、域名相同、埠不同 | 不允許 | http://www.monkey.com/js/a.js | https://www.monkey.com/js/b.js | 域名相同、埠相同,協議不同 | 不允許 | http://www.monkey.com/js/a.js | http://192.168.0.118/js/b.js | 域名和域名對應的ip地址 | 不允許 | http://www.monkey.com/a.js | http://monkey.com/b.js | 主域相同,子域不同 | 不允許 | http://www.monkey.com/a.js | http://www.monkey1.com/b.js | 不同域名 | 不允許 | > 此時,不允許同通訊的頁面之間想要實現通訊,就要使用到跨域了。 ### 常見跨域方案 * 1、 通過jsonp跨域 * 2、 document.domain+iframe跨域 * 3、 location.hash + iframe * 4、 window.name + iframe跨域 * 5、 postMessage跨域 * 6、 跨域資源共享(CORS) * 7、 nginx代理跨域 * 8、 nodejs中介軟體代理跨域 * 9、 WebSocket協議跨域 #### jsonp跨域 在頁面中通過==script==標籤載入資源,是被瀏覽器所允許的,也不存在跨域的問題,基於這一原理,我們可以通過動態的建立過==script==標籤,然後==src==賦值一個帶參的url,進而實現跨域,也叫jsonp跨域。實現方式如下: ##### 原生實現方式 ``` function callback(res){ console.log(res)//介面返回值 } let jsonp = document.createElement('script'); jsonp.src = 'http:/www.monkey.com/admin/getUser?name=小燕子&callback=callback'; document.getElementsByTagName('head')[0].appendChild(jsonp);//新增到頁面中 jsonp.remove();//從頁面中移除 ``` ##### jQuery實現 * 第一種:$.ajax()方法 ``` $.ajax({ url:'http:/www.monkey.com/admin/getUser', dataType:"jsonp", jsonp: "callback",//請求時路徑引數名 jsonpCallback:"callback",//設定後端返回函式名 success:function(data){//回撥函式 console.log(data); } }); ``` * 第二種:$.get()方法 ``` $.get('http:/www.monkey.com/admin/getUser', {各種資料}, function(data) { console.log(data); }, 'jsonp'); ``` > 推薦使用$.get()方法 ##### 後端node.js核心程式碼示例: ``` // jsonp返回設定 res.writeHead(200,{'Content-Type':'text/javascript'}); res.write(callback+'('+JSON..stringify(response) + ')'); ``` > jsonp跨域只能實現get請求 #### document.domain+iframe跨域 運用此方法跨域必須有個前提: 這兩個域名必須屬於同一個一級域名!而且所用的協議,埠都要一致,否則無法利用document.domain進行跨域。 Javascript出於對安全性的考慮,而禁止兩個或者多個不同域的頁面進行互相操作。 而相同域的頁面在相互操作的時候不會有任何問題。 實現方式: * 父頁面(http://www.monkey.com/a.html) ``` ``` * 子頁面(http://monkey.com/b.html) ``` ``` #### location.hash + iframe 當兩個不同域的頁面之間想要實現通訊的時候,可以通過與其中一個頁面同域的第三個頁面實現。因為改變hash值,不會重新整理頁面,所以可以通過hash值進行傳遞引數。 畫個草圖幫助理解: ![](https://img2020.cnblogs.com/blog/2050700/202007/2050700-20200731105020229-106503056.png) 實現方式: * a.html(http://www.monkey.com/a.html) ``` ``` * b.html(http://www.monkey1.com/b.html) ``` ``` * c.html(http://www.monkey.com/c.html) ``` ``` > 此方法有個缺點,就是所有傳遞的資料都暴露在了url中。 #### window.name + iframe跨域 window.name的值再不同的頁面(包括不同域名)載入後依然存在,並且支援的資料量非常可觀,最大2MB。 > 簡單來說就是頁面重新整理之後,window.name值依舊存在 因此我們可以基於這個原理,我們可以巧妙的實現跨域,並且同時它也是安全操作。 實現方式: * a.html(http://www.monkey.com/a.html) ``` var proxy = function(url, callback) { var state = 0; var iframe = document.createElement('iframe'); // 載入跨域頁面 iframe.src = url; // onload事件會觸發2次,第1次載入跨域頁,並留存資料於window.name iframe.onload = function() { if (state === 1) { // 第2次onload(同域proxy頁)成功後,讀取同域window.name中資料 callback(iframe.contentWindow.name); destoryFrame(); } else if (state === 0) { // 第1次onload(跨域頁)成功後,切換到同域代理頁面 iframe.contentWindow.location = 'http://www.domain1.com/proxy.html'; state = 1; } }; document.body.appendChild(iframe); // 獲取資料以後銷燬這個iframe,釋放記憶體;這也保證了安全(不被其他域frame js訪問) function destoryFrame() { iframe.contentWindow.document.write(''); iframe.contentWindow.close(); document.body.removeChild(iframe); } }; // 請求跨域b頁面資料 proxy('http://www.domain2.com/b.html', function(data){ alert(data); }); ``` * proxy.html(http://www.monkey.com/proxy.html) 中間代理頁,與a.html同域,內容為空即可。 * b.html(http://www.monkey1.com/b.html) ``` ``` #### postMessage跨域 隨著HTML5的發展,html5工作組提供了兩個重要的介面:postMessage(send) 和 onmessage。這兩個介面有點類似於websocket,可以實現兩個跨域站點頁面之間的資料傳遞。 postMessage(data,origin),接收兩個引數: > data是傳輸的資料,部分瀏覽器只支援字串,因此最好傳參時使用JSON.stringify()序列化。 > origin: 協議+主機+埠號,也可以設定為"*",表示可以傳遞給任意視窗,如果要指定和當前視窗同源的話設定為"/"。 實現方式: * a.html(http://www.monkey.com/a.html) ``` ``` * b.html(http://www.monkey1.com/b.html) ``` ``` #### 跨域資源共享(CORS) 只需要服務端設定Access-Control-Allow-Origin即可,前端無須設定 #### nginx反向代理跨域 nginx在本地搭建一個服務向遠端伺服器請求資料,前提是前後端分離的條件下,這樣後端可以上傳他的介面到伺服器,或者你可以訪問後臺本地的環境也是可以的。 * 下載nginx [http://nginx.org/en/download.html](http://nginx.org/en/download.html) 下載任意一個版本即可,我下載的是nginx-1.12.2,下載之後解壓即可。 * 配置代理 ``` server { listen 8080; server_name localhost; #charset koi8-r; access_log logs/k8s.log; location / { root D:/_test/front/app; #你專案的根目錄 index index.html index.htm; } ## 使用者訪問 localhost/api,則反向代理到http://www.monkeysoft.com location /api/ { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-Nginx-Proxy true; proxy_set_header Connection ""; proxy_pass http://www.monkeysoft.com; #****這裡填上伺服器地址 proxy_redirect default ; } } ``` * 啟動服務 配置好了啟動nginx伺服器就好了。在這裡整理一些windows常用的命令: ``` 1、啟動: D:\nginx-1.12.2>start nginx或 D:\nginx-1.12.2>nginx.exe 2、停止: D:\nginx-1.12.2>nginx.exe -s stop或 D:\nginx-1.12.2>nginx.exe -s quit 注:stop是快速停止nginx,可能並不儲存相關資訊;quit是完整有序的停止nginx,並儲存相關資訊。 3、重新載入Nginx: D:\nginx-1.12.2>nginx.exe -s reload 當配置資訊修改,需要重新載入這些配置時使用此命令。 4、重新開啟日誌檔案: D:\nginx-1.12.2>nginx.exe -s reopen 5、檢視Nginx版本: D:\nginx-1.12.2>nginx -v ``` #### 伺服器代理跨域 伺服器代理一般是通過node中介軟體http-proxy-middleware實現的,在vue、react腳手架中,已經集成了該中介軟體,我們只需要配置使用既可。 webpack配置檔案部分程式碼: ``` devServer: { proxy: { '/api': { target: 'http://www.monkey.com:8080',//真實請求地址 changeOrigin: true, // 預設false,是否需要改變原始主機頭為目標URL ws: true,// 是否代理websocket pathRewrite: {//重寫路徑 '^/api': '' }, router: { // 如果請求主機 == 'dev.localhost:3000', // 重寫目標伺服器 'http://www.example.org' 為 'http://localhost:8000' 'dev.localhost:3000' : 'http://localhost:8000' } }, } }, ``` #### WebSocket協議跨域。 WebSocket protocol是HTML5一種新的協議。它實現了瀏覽器與伺服器全雙工通訊,同時允許跨域通訊,是server push技術的一種很好的實現。 實現方式 頁面端(客戶端): ``` ``` 服務端(node): ``` //引入http標準模組,CommonJS模組 const http = require("http"); const fs = require("fs"); const ws = require("socket.io"); //建立一個web服務 const server = http.createServer(function(request,response){ response.writeHead(200,{ "Content-type":"text/html;charset=UTF-8" }) // 可傳送文字 // response.end("hello world"); // 可自動解析html response.end("

我是標題2

"); }) //基於當前web服務開啟socket例項 const io = ws(server) //檢測連線事件 io.on("connection",function(socket){ //接收客戶端所傳送的訊息 socket.on("message",function(message){ console.log(message) //向所有客戶端廣播該訊息 io.emit("message",message) }) //監聽到斷開連結 socket.on("disconnect",function(){ //傳送廣播 某使用者離開了群聊 }) }) //服務端監聽埠 server.listen(8080) ``` 參考: 1、前端常見跨域解決方案(全) https://segmentfault.com/a/1190000011145364 2、前端專案中nginx 本地反向代理配置 https://www.jianshu.com/p/5c23