瀏覽器的同源策略會致使跨域,這裏同源策略又分爲如下兩種:css
DOM同源策略:禁止對不一樣源頁面DOM進行操做。這裏主要場景是iframe跨域的狀況,不一樣域名的iframe是限制互相訪問的。html
XmlHttpRequest同源策略:禁止使用XHR對象向不一樣源的服務器地址發起HTTP請求。 只要協議、域名、端口有任何一個不一樣,都被看成是不一樣的域,之間的請求就是跨域操做。前端
瞭解完跨域以後,想必你們都會有這麼一個思考,爲何要有跨域的限制,瀏覽器這麼作是出於何種緣由呢。其實仔細想想就會明白,跨域限制主要是爲了安全考慮。vue
常見的跨域場景:html5
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)AJAX同源策略主要用來防止CSRF攻擊。若是沒有AJAX同源策略,至關危險,咱們發起的每一次HTTP請求都會帶上請求地址對應的cookie,那麼能夠作以下攻擊:java
一、用戶登陸了本身的銀行頁面http://mybank.com,http://mybank.com向用戶的cookie中添加用戶標識。node
二、用戶瀏覽了惡意頁面 http://evil.com。執行了頁面中的惡意AJAX請求代碼。webpack
三、http://evil.com向http://mybank.com發起AJAXHTTP請求,請求會默認把http://mybank.com對應cookie也同時發送過去。nginx
四、銀行頁面從發送的cookie中提取用戶標識,驗證用戶無誤,response中返回請求數據。此時數據就泄露了。web
五、並且因爲Ajax在後臺執行,用戶沒法感知這一過程。
(2)DOM同源策略也同樣,若是iframe之間能夠跨域訪問,能夠這樣攻擊:
一、作一個假網站,裏面用iframe嵌套一個銀行網站 http://mybank.com。
二、把iframe寬高啥的調整到頁面所有,這樣用戶進來除了域名,別的部分和銀行的網站沒有任何差異。
三、這時若是用戶輸入帳號密碼,咱們的主網站能夠跨域訪問到http://mybank.com的dom節點,就能夠拿到用戶的輸入了,那麼就完成了一次攻擊。 因此說有了跨域跨域限制以後,咱們才能更安全的上網了。
(1)跨域資源共享
CORS是一個W3C標準,全稱是」跨域資源共享」(Cross-origin resource sharing)。 對於這個方式,阮一峯老師總結的文章特別好,但願深刻了解的能夠看一下http://www.ruanyifeng.com/blog/2016/04/cors.html。
這裏簡單的說一說大致流程。
一、對於客戶端,咱們仍是正常使用xhr對象發送ajax請求。 惟一須要注意的是,咱們須要設置咱們的xhr屬性withCredentials爲true,否則的話,cookie是帶不過去的,設置: xhr.withCredentials = true;
二、對於服務器端,須要在 response header中設置以下兩個字段: Access-Control-Allow-Origin: http://www.yourhost.com Access-Control-Allow-Credentials:true 這樣,咱們就能夠跨域請求接口了。
(2)jsonp實現跨域
基本原理就是經過動態建立script標籤,而後利用src屬性進行跨域。
這麼說比較模糊,咱們來看個例子:
1 // 定義一個fun函數 2 function fun(fata) { 3 console.log(data); 4 }; 5 // 建立一個腳本,而且告訴後端回調函數名叫fun 6 var body = document.getElementsByTagName('body')[0]; 7 var script = document.gerElement('script'); 8 script.type = 'text/javasctipt'; 9 script.src = 'demo.js?callback=fun'; 10 body.appendChild(script);
返回的js腳本,直接會執行。因此就執行了事先定義好的fun函數了,而且把數據傳入了進來。
fun({"name": "name"})
固然,這個只是一個原理演示,實際狀況下,咱們須要動態建立這個fun函數,而且在數據返回的時候銷燬它。 由於在實際使用的時候,咱們用的各類ajax庫,基本都包含了jsonp的封裝,不過咱們仍是要知道一下原理,否則就不知道爲何jsonp不能發post請求了~
(3)document.domain + iframe跨域
此方案僅限主域相同,子域不一樣的跨域應用場景。
實現原理:兩個頁面都經過js強制設置document.domain爲基礎主域,就實現了同域。
(1)父窗口:(http://www.domain.com/a.html)
1 <iframe id="iframe" src="http://child.domain.com/b.html"></iframe> 2 <script> 3 document.domain = 'domain.com'; 4 var user = 'admin'; 5 </script>
(2)子窗口:(http://child.domain.com/b.html)
1 <script> 2 document.domain = 'domain.com'; 3 // 獲取父窗口中變量 4 alert('get js data from parent ---> ' + window.parent.user); 5 </script>
(4)window.name + iframe跨域
window對象擁有name屬性,它有一個特色:相同協議下,在一個頁面中,不隨URL的改變而改變
經過window.name實現跨域也很簡單,iframe擁有contentWindow屬性,其指向該iframe的window對象的引用,若是在iframe的src指向的頁面中設置window.name值,那麼就能夠經過iframe.contentWindow.name就能夠拿到這個值了
1 var url = "http://funteas.com/lab/windowName"; 2 var iframe = document.createElement('iframe') 3 iframe.onload = function(){ 4 var data = iframe.contentWindow.name 5 console.log(data) 6 } 7 iframe.src = url 8 document.body.appendChild(iframe)
然而,chrome會提示你跨域了! 而咱們已經知道window.name不隨URL的改變而改版,即onload時,已經獲取到了name,只不過由於不一樣源,當前頁面的腳本沒法拿到iframe.contentWindow.name,此時只須要把iframe.src改成同源便可
1 var url = "http://funteas.com/lab/windowName"; 2 var iframe = document.createElement('iframe') 3 iframe.onload = function(){ 4 iframe.src = 'favicon.ico'; 5 var data = iframe.contentWindow.name 6 console.log(data) 7 } 8 iframe.src = url 9 document.body.appendChild(iframe)
(5)location.hash + iframe跨域
實現原理: a欲與b跨域相互通訊,經過中間頁c來實現。 三個頁面,不一樣域之間利用iframe的location.hash傳值,相同域之間直接js訪問來通訊。
具體實現:A域:a.html -> B域:b.html -> A域:c.html,a與b不一樣域只能經過hash值單向通訊,b與c也不一樣域也只能單向通訊,但c與a同域,因此c可經過parent.parent訪問a頁面全部對象。
(1)a.html:(http://www.domain1.com/a.html)
1 <iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe> 2 <script> 3 var iframe = document.getElementById('iframe'); 4 5 // 向b.html傳hash值 6 setTimeout(function() { 7 iframe.src = iframe.src + '#user=admin'; 8 }, 1000); 9 10 // 開放給同域c.html的回調方法 11 function onCallback(res) { 12 alert('data from c.html ---> ' + res); 13 } 14 </script>
(2)b.html:(http://www.domain2.com/b.html)
1 <iframe id="iframe" src="http://www.domain1.com/c.html" style="display:none;"></iframe> 2 <script> 3 var iframe = document.getElementById('iframe'); 4 5 // 監聽a.html傳來的hash值,再傳給c.html 6 window.onhashchange = function () { 7 iframe.src = iframe.src + location.hash; 8 }; 9 </script>
(3)c.html:(http://www.domain1.com/c.html)
1 <script> 2 // 監聽b.html傳來的hash值 3 window.onhashchange = function () { 4 // 再經過操做同域a.html的js回調,將結果傳回 5 window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', '')); 6 }; 7 </script>
(6)postMessage跨域
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是爲數很少能夠跨域操做的window屬性之一,它可用於解決如下方面的問題:
(a)頁面和其打開的新窗口的數據傳遞(b)多窗口之間消息傳遞(c)頁面與嵌套的iframe消息傳遞
用法:otherWindow.postMessage(message,targetOrigin);
postMessage(data,origin)方法接受兩個參數:
data: html5規範支持任意基本類型或可複製的對象,但部分瀏覽器只支持字符串,因此傳參時最好用JSON.stringify()序列化。
origin: 協議+主機+端口號,也能夠設置爲"*",表示能夠傳遞給任意窗口,若是要指定和當前窗口同源的話設置爲"/"。
(1)a.html:(http://www.domain1.com/a.html))
1 <iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe> 2 <script> 3 var iframe = document.getElementById('iframe'); 4 iframe.onload = function() { 5 var data = { 6 name: 'aym' 7 }; 8 // 向domain2傳送跨域數據 9 iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com'); 10 }; 11 12 // 接受domain2返回數據 13 window.addEventListener('message', function(e) { 14 alert('data from domain2 ---> ' + e.data); 15 }, false); 16 </script>
(2)b.html:(http://www.domain2.com/b.html)
1 <script> 2 // 接收domain1的數據 3 window.addEventListener('message', function(e) { 4 alert('data from domain1 ---> ' + e.data); 5 6 var data = JSON.parse(e.data); 7 if (data) { 8 data.number = 16; 9 10 // 處理後再發回domain1 11 window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com'); 12 } 13 }, false); 14 </script>
(7)WebSocket協議跨域
WebSocket protocol是HTML5一種新的協議。它實現了瀏覽器與服務器全雙工通訊,同時容許跨域通信,是server push技術的一種很好的實現。 原生WebSocket API使用起來不太方便,咱們使用Socket.io,它很好地封裝了webSocket接口,提供了更簡單、靈活的接口,也對不支持webSocket的瀏覽器提供了向下兼容。
(1)前端代碼
1 <div>user input:<input type="text"></div> 2 <script src="./socket.io.js"></script> 3 <script> 4 var socket = io('http://www.domain2.com:8080'); 5 6 // 鏈接成功處理 7 socket.on('connect', function() { 8 // 監聽服務端消息 9 socket.on('message', function(msg) { 10 console.log('data from server: ---> ' + msg); 11 }); 12 13 // 監聽服務端關閉 14 socket.on('disconnect', function() { 15 console.log('Server socket has closed.'); 16 }); 17 }); 18 19 document.getElementsByTagName('input')[0].onblur = function() { 20 socket.send(this.value); 21 }; 22 </script>
(2)Nodejs socket後臺
1 var http = require('http'); 2 var socket = require('socket.io'); 3 4 // 啓http服務 5 var server = http.createServer(function(req, res) { 6 res.writeHead(200, { 7 'Content-type': 'text/html' 8 }); 9 res.end(); 10 }); 11 12 server.listen('8080'); 13 console.log('Server is running at port 8080...'); 14 15 // 監聽socket鏈接 16 socket.listen(server).on('connection', function(client) { 17 // 接收信息 18 client.on('message', function(msg) { 19 client.send('hello:' + msg); 20 console.log('data from client: ---> ' + msg); 21 }); 22 23 // 斷開處理 24 client.on('disconnect', function() { 25 console.log('Client socket has closed.'); 26 }); 27 });
(8)nginx代理跨域
一、nginx配置解決iconfont跨域
瀏覽器跨域訪問js、css、img等常規靜態資源被同源策略許可,但iconfont字體文件(eot|otf|ttf|woff|svg)例外,此時可在nginx的靜態資源服務器中加入如下配置。
1 location / { 2 add_header Access-Control-Allow-Origin *; 3 }
二、nginx反向代理接口跨域
跨域原理: 同源策略是瀏覽器的安全策略,不是HTTP協議的一部分。服務器端調用HTTP接口只是使用HTTP協議,不會執行JS腳本,不須要同源策略,也就不存在跨越問題。
實現思路:經過nginx配置一個代理服務器(域名與domain1相同,端口不一樣)作跳板機,反向代理訪問domain2接口,而且能夠順便修改cookie中domain信息,方便當前域cookie寫入,實現跨域登陸。
nginx具體配置:
1 #proxy服務器 2 server { 3 listen 81; 4 server_name www.domain1.com; 5 6 location / { 7 proxy_pass http://www.domain2.com:8080; #反向代理 8 proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie裏域名 9 index index.html index.htm; 10 11 # 當用webpack-dev-server等中間件代理接口訪問nignx時,此時無瀏覽器參與,故沒有同源限制,下面的跨域配置可不啓用 12 add_header Access-Control-Allow-Origin http://www.domain1.com; #當前端只跨域不帶cookie時,可爲* 13 add_header Access-Control-Allow-Credentials true; 14 } 15 }
(1)前端代碼示例:
1 var xhr = new XMLHttpRequest(); 2 3 // 前端開關:瀏覽器是否讀寫cookie 4 xhr.withCredentials = true; 5 6 // 訪問nginx中的代理服務器 7 xhr.open('get', 'http://www.domain1.com:81/?user=admin', true); 8 xhr.send();
(2)Nodejs後臺示例:
1 var http = require('http'); 2 var server = http.createServer(); 3 var qs = require('querystring'); 4 5 server.on('request', function(req, res) { 6 var params = qs.parse(req.url.substring(2)); 7 8 // 向前臺寫cookie 9 res.writeHead(200, { 10 'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly:腳本沒法讀取 11 }); 12 13 res.write(JSON.stringify(params)); 14 res.end(); 15 }); 16 17 server.listen('8080'); 18 console.log('Server is running at port 8080...');
(9)Nodejs中間件代理跨域
node中間件實現跨域代理,原理大體與nginx相同,都是經過啓一個代理服務器,實現數據的轉發,也能夠經過設置cookieDomainRewrite參數修改響應頭中cookie中域名,實現當前域的cookie寫入,方便接口登陸認證。
一、 非vue框架的跨域(2次跨域)
利用node + express + http-proxy-middleware搭建一個proxy服務器。
(1)前端代碼示例:
1 var xhr = new XMLHttpRequest(); 2 3 // 前端開關:瀏覽器是否讀寫cookie 4 xhr.withCredentials = true; 5 6 // 訪問http-proxy-middleware代理服務器 7 xhr.open('get', 'http://www.domain1.com:3000/login?user=admin', true); 8 xhr.send();
(2)中間件服務器:
1 var express = require('express'); 2 var proxy = require('http-proxy-middleware'); 3 var app = express(); 4 5 app.use('/', proxy({ 6 // 代理跨域目標接口 7 target: 'http://www.domain2.com:8080', 8 changeOrigin: true, 9 10 // 修改響應頭信息,實現跨域並容許帶cookie 11 onProxyRes: function(proxyRes, req, res) { 12 res.header('Access-Control-Allow-Origin', 'http://www.domain1.com'); 13 res.header('Access-Control-Allow-Credentials', 'true'); 14 }, 15 16 // 修改響應信息中的cookie域名 17 cookieDomainRewrite: 'www.domain1.com' // 能夠爲false,表示不修改 18 })); 19 20 app.listen(3000); 21 console.log('Proxy server is listen at port 3000...');
(3)Nodejs後臺示例:同nginx中
二、 vue框架的跨域(1次跨域)
利用node + webpack + webpack-dev-server代理接口跨域。在開發環境下,因爲vue渲染服務和接口代理服務都是webpack-dev-server同一個,因此頁面與代理接口之間再也不跨域,無須設置headers跨域信息了。
webpack.config.js部分配置:
1 module.exports = { 2 entry: {}, 3 module: {}, 4 ... 5 devServer: { 6 historyApiFallback: true, 7 proxy: [{ 8 context: '/login', 9 target: 'http://www.domain2.com:8080', // 代理跨域目標接口 10 changeOrigin: true, 11 cookieDomainRewrite: 'www.domain1.com' // 能夠爲false,表示不修改 12 }], 13 noInfo: true 14 } 15 }
總結:以上跨域詳解摘自不一樣的專欄整理而成,實際狀況下,通常用cors,jsonp等常見方法就能夠了。不過遇到了一些很是規狀況,咱們仍是須要知道有更多的方法能夠選擇的。