什麼是跨域?如何解決跨域問題
一、什麼是跨域?
指的是瀏覽器不能執行其他網站的指令碼,它是由瀏覽器的同源策略造成的,是瀏覽器對 javascript 施加的安全限制。
同源策略:是指協議(protocol)、域名(host)、埠號(port),都必須相同,其中一個不同都會產生跨域。
http://www.test.com:8000/ 協議(http)、主域名(test)、子域名(www)、埠號(8000)
二、非同源限制
無法讀取非同源網頁的cookie、localStorage、IndexedDB
無法接觸非同源網頁的DOM
無法向非同源地址傳送 AJAX 請求
三、解決跨域的方法
1.JSONP
原理是:
有些標籤 script、img、link、iframe ... 這些標籤不存在跨域請求的限制,就是利用這個特點解決跨域問題。
JSONP 是伺服器與客戶端跨源通訊的常用方法。
優點:簡單適用,相容性好(可以相容低版本IE),
缺點:只支援 get 請求,不支援 post 請求,導致資料不安全
核心思想:網頁通過新增一個 <script> 元素,向伺服器請求 JSON 資料,伺服器收到請求後,將資料放在一個指定名字的回撥函式的引數位置傳回來。
① 原生實現(JSONP 需要服務端的支援)
<script src='http://test.com/data?callback=func'></script> <!-- 向伺服器 test.com 發出請求,該請求的查詢字串有一個 callback 引數,用來指定回撥函式的名字 --> <!-- 給客戶端返回資料 "func('+JSON.stringify(data)+')" ,瀏覽器把字串變成 js 表示式執行 --> <!-- func 需要是一個全域性函式 --> <script type='text/javascript'> functionfunc(res){ // 處理獲得的資料 } </script>
② jQuery 提供了 JSONP 的處理方式
$.ajax({ url: 'http://www.test.com:8000/login', type: 'get', dataType: 'jsonp',// 設定請求方式為 jsonp jsonpCallback: 'handleCallback',// 自定義回撥函式名 data: {} })
③ Vue.js
this.$http.jsonp('http://www.test.com:8080/login', { params: {}, jsonp: 'handleCallback' }).then(res => { // });
2.CORS 跨域資源共享(Cross-Origin Resource Sharing)。
它是新的 W3C 標準,它新增的一組 HTTP 首部欄位允許伺服器其宣告那些來源請求有權訪問哪些資源,就是它允許瀏覽器向其聲明瞭 CORS 的棧進行跨域請求。
這種方式最主要的特點就是會在響應的 HTTP 首部增加 Access-Control-Allow-Origin 等資訊,從而判定那些資源站可以進行跨域請求,還有幾個其他相關的 HTTP 首部進行更加精細化的控制,最主要的還是 Access-Control-Allow-Origin 。
CORS 與 JSONP 對比來說又是比較明顯,JSONP 只支援 GET 方式,而且 JSONP 並不符合處理業務的正常流程。採用 CORS 的方式,前端編碼與正常非跨域請求沒有什麼不同。
客戶端傳送請求(ajax)
在真正的傳送跨域請求之前會發送一個試探性請求(OPTIONS),伺服器接收到 OPTIONS請求之後,做一個處理,返回成功,表示可以傳送跨域請求,再發送真正的跨域請求
服務端設定相關的頭資訊(需要處理試探性請求OPTIONS)
2.帶 cookie 跨域請求:前後端都需要進行設定
前端設定:根據 xhr.withCredentials 欄位判斷是否帶有 cookie
① 原生 ajax
var xhr = new XMLHttpRequest();// ie8/9 需用 window.XDomainRequest 相容 // 前端設定是否帶 cookie xhr.withCredentials = true; xhr.open('post', 'http://www.test.com:8000/index', true); xhr.setRequestHeader('Content-ype', 'application/x-www-form-urlencoded'); xhr.send('user=admin'); xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { alert(xhr.responseText) } };
② jQuery ajax
$.ajax({ url: 'http://www.test.com:8000/index', type: 'get', data: {}, xhrFields: { withCredentials: true// 設定前端是否帶 cookie }, crossDomain: true// 會讓請求頭中包含跨域的額外資訊,但不會含 cookie });
③ vue-resource
Vue.http.options.credentials = true
④ axios
axios.defaults.withCredentials = true
伺服器設定
伺服器端對於 CORS 的支援,主要是通過設定 Access-Control-Allow-Origin 來進行的。如果瀏覽器檢測到相應的設定,就可以允許 Ajax 進行跨域訪問。
// 不使用*,自動適配跨域域名,避免攜帶Cookie時失效 String origin = request.getHeader('Origin'); if (StringUtils.isNotBlank(origin)) { response.setHeader('Access-Control-Allow-Origin', origin); } // 自適應所有自定義頭 String headers = request.getHeader('Access-Control-Request-Headers'); if (StringUtils.isNotBlank(headers)) { response.setHeader('Access-Control-Allow-Headers', headers); response.setHeader('Access-Control-Expose-Headers', headers); } // 允許跨域的請求方法型別 response.setHeader('Access-Control-Allow-Methods', '*'); // 預檢命令(OPTIONS)快取時間,單位:秒 response.setHeader('Access-Control-Max-Age', '3600'); // 明確許可客戶端傳送Cookie,不允許刪除欄位即可 response.setHeader('Access-Control-Allow-Credentials', 'true');
3.反向代理
既然不能跨域請求,那麼我們不跨域就可以了,通過在請求到達伺服器前部署一個服務,將介面請求進行轉發,這就是反向代理。通過一定的轉發規則可以將前端的請求轉發到其他的服務。
通過反向代理我們將前後端專案統一通過反向代理來提供對外的服務,這樣在前端看上去就跟不存在跨域一樣。
反向代理麻煩之處就在原對 Nginx 等反向代理服務的配置,在目前前後端分離的專案中很多都是採用這種方式。
proxyTable: { '/api': { target:'http://api.douban.com/v2', // 你請求的第三方介面 changeOrigin:true, // 在本地會建立一個虛擬服務端,然後傳送請求的資料,並同時接收請求的資料,這樣服務端和服務端進行資料的互動就不會有跨域問題 pathRewrite:{ // 路徑重寫, '^/api': '' // 替換target中的請求地址,也就是說以後你在請求http://api.douban.com/v2/XXXXX這個地址的時候直接寫成/api即可。 } } },