什么是跨域
跨域:一個域下的文檔或腳本試圖去請求另一個域下的資源
廣義的跨域包含一下內容:
1.資源跳轉(鏈接跳轉,重定向跳轉,表單提交)
2.資源請求(內部的引用,腳本script,圖片img,frame)
3.script內部發起的請求(ajax,dom請求,和js跨域調用
跨域問題出現:
只有瀏覽器端出現,直接用終端請求,是不會出現跨域攔截,是屬於瀏覽器端的安全策略,瀏覽器將不同源的請求進行了攔截,限制了跨域資源訪問
什么是同源
同源策略:same origin policy,如果兩個資源(頁面)`協議`,`域名`,`端口`都相同,那么就是同源。
即使:兩個不同域名指向同一個ip,也算非同源
非同源(跨域)有哪些限制
* cookie,localstorage,indexDB無法讀取
* Dom和JS對象無法互通
* Ajax請求不能發送
常見的跨域demo
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 不同域名 不允許
前端解決跨域的方法
-
JSONP
- 原理:由於在一個頁面內部js如果要訪問另一個非同源域的數據是被瀏覽器限制的,但是瀏覽器在解析和加載script標簽的時候,是允許一個頁面加載多個域的js標簽,而js標簽其實又是類似一種get請求的方式,只是返回的數據是一個JSON格式的腳本對象。
- 用法:將get請求封裝到script標簽中,利用script標簽做get請求,最后解析script標簽數據
- 代碼:
var script = document.createElement('script') script.style = 'text/javascript' //將要請求的get地址傳到script的src屬性,並且添加回調函數 script.setAttribute('scr','http://www.domain2.com:8080/login?user=admin&callback=handleCallback') //將script標簽放入文檔 document.head.appendChild(script) //執行回調函數 function handleCallback(res){ //處理JSON格式數據 alert(JSON.stringify(res)) }
- 由於請求script只能是get方法,所以JSONP這種方式只能解決get請求,post或者其他http方法無法解決
-
跨域資源共享
- 原理:
- cors:cross-origin -resouce sharing跨域資源共享,允許瀏覽器向跨源服務器發出http請求,需要瀏覽器和服務器同時支持
- 瀏覽器在發出跨域請求時:1。簡單請求,直接在頭部信息中帶上origin信息,標識屬於哪個源,服務端配置
access-control-allow-origin
接受哪些域名跨域訪問,可以是*
允許所有 - 如果是非簡單請求會在正式請求之前,發送預檢請求
- 簡單來說(簡單請求):
- 是一個雙端配合,允許打卡跨域資源限制的一種手段
- 瀏覽器端:請求頭攜帶origin信息
- 服務端:配置
access-control-allow-origin
允許哪些源跨域
- 代碼:
- 原生:
xmlHttpRequest
不需要配置請求頭,簡單請求會自動帶上origin屬性var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容 xhr.open('post', 'http://www.domain2.com:8080/login', true); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send('user=admin'); xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { alert(xhr.responseText); } };
- AJAX:有一個屬性
crossDomain
,配置為true之后會讓請求頭攜帶orgin跨域額外信息,但是不會自動包含cookie$.ajax({ ... xhrFields: { withCredentials: true // 前端設置是否帶cookie }, crossDomain: true, // 會讓請求頭中包含跨域的額外信息,但不會含cookie ... });
- VUE中的axios,默認也會把origin放到請求頭,不需要額外配置
- 原生:
- 服務端配置
/* * 導入包:import javax.servlet.http.HttpServletResponse; * 接口參數中定義:HttpServletResponse response */ // 允許跨域訪問的域名:若有端口需寫全(協議+域名+端口),若沒有端口末尾不用加'/' response.setHeader("Access-Control-Allow-Origin", "http://www.domain1.com"); // 允許前端帶認證cookie:啟用此項后,上面的域名不能為'*',必須指定具體的域名,否則瀏覽器會提示 response.setHeader("Access-Control-Allow-Credentials", "true"); // 提示OPTIONS預檢時,后端需要設置的兩個常用自定義頭 response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With");
- 原理:
-
反向代理(NodeJs中間件代理跨域)(Vue代理跨域)(nginx轉發代理)
-
場景:當服務器端無法修改cors,並且client全部換成jsonp方式比較繁瑣時,只能通過中間代理服務器,將代理服務器設置為支持CORS或者代理服務器和請求頁面同源,使頁面可以直接請求代理服務器,在通過代理服務器進行接口代理,代理請求目標接口地址,返回數據
-
原理:跨域限制僅是瀏覽器端的安全策略,並不是http協議的固有限制,所以中間服務器在參數和cookie有效的情況下是可以正常的請求目標服務器的,Vue(
node+webpack+webpack-dev-server
)中配置proxy
就是啟動一個同源的服務進行接口代理 -
注意:
- 1.非vue ,webpack,dev-serve服務,單獨啟的nginx或者node express服務做反向代理時,一般是跟頁面非同源,非同源要訪問代理服務器也存在跨域問題,需要配置
cors
允許跨域訪問,配置access-control-allow-header
- 在webpack-dev-server服務,默認應該是基本與測試環境頁面同源,不需要配置請求頭允許
- 1.非vue ,webpack,dev-serve服務,單獨啟的nginx或者node express服務做反向代理時,一般是跟頁面非同源,非同源要訪問代理服務器也存在跨域問題,需要配置
-
代碼:
- webpack-dev-server代理
module.exports = { entry: {}, module: {}, ... devServer: { historyApiFallback: true, proxy: [{ context: '/login', target: 'http://www.domain2.com:8080', // 代理跨域目標接口 changeOrigin: true, secure: false, // 當代理某些https服務報錯時用 cookieDomainRewrite: 'www.domain1.com' // 可以為false,表示不修改 }], noInfo: true } }
- node express反向代理
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...');
- 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; } }
-
-
其他js HACK方法
-
主域相同子域不同場景:docment.domain+iframe
-
三個頁面跨域互傳 : location.hash + iframe
-
window.name + iframe
-
H5新增的postMessage(跨窗口消息互通方法)
-
關於iframe:
- 其實相當於在當前頁面下新開了個子頁面。遵循一切頁面與頁面之間資源訪問原則,例如
- 1.當主頁面和iframe頁面不同源時,無法互相訪問DOM
- 2.當主域一樣,二級域名不同時,document.domain的設置,可以規避同源策略,可以互相訪問DOM
- 其實相當於在當前頁面下新開了個子頁面。遵循一切頁面與頁面之間資源訪問原則,例如
-
location.hash:
- url 末尾
#
后面跟隨的時頁面片段標識符,用來表示瀏覽器渲染哪部分頁面信息,但是改變這個值頁面不會重新刷新, 並且設置之后,所有頁面可以通過document對象訪問該窗口的location 信息進行獲取,可以實現巧妙跨域 - 代碼:
//父窗口可以把信息,寫入子窗口的片段標識符。 var src = originURL + ‘#’ + data; document.getElementById(‘myIFrame’).src = src; //子窗口通過監聽hashchange事件得到通知。 window.onhashchange = checkMessage; function checkMessage() { var message = window.location.hash; // … }
- url 末尾
-
同理,window.name也是一種hack方法實現跨域的方式
-
瀏覽器窗口有window.name屬性。這個屬性的最大特點是,無論是否同源,只要在同一個窗口里,前一個網頁設置了這個屬性,后一個網頁可以讀取它。
父窗口先打開一個子窗口,載入一個不同源的網頁,該網頁將信息寫入window.name屬性。
window.name = data;
接着,子窗口跳回一個與主窗口同域的網址。
location = ‘http://parent.url.com/xxx.html’;
然后,主窗口就可以讀取子窗口的window.name了。
var data = document.getElementById(‘myFrame’).contentWindow.name;
這種方法的優點是,window.name容量很大,可以放置非常長的字符串;缺點是必須監聽子窗口window.name屬性的變化,影響網頁性能。
-
-
postMessage
- window.name和location.hash都屬於取巧的方式,利用了瀏覽器頁面的非數據屬性或者是窗口的固有屬性達到跨頁面的目的,但是在H5新增了postMessage API,實現跨tab跨窗口的數據通信,允許窗口通信,並且不論是否同源
-
總結:
常用的,還是代理,還有CORS,或者是JSONP這三種方式,禁止跨域本來就是瀏覽器的一種安全策略,雖然有一定限制開發者的手腳,但是在一些特殊的網站或者安全性要求比較高的網站,網絡安全還是不可忽視的
非常感謝:下面的文章給了我很多的幫助,感謝各位前行者的辛苦付出,可以點擊查閱更多信息