一、什么是跨域?
指的是瀏覽器不能執行其他網站的腳本,它是由瀏覽器的同源策略造成的,是瀏覽器對 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'> function func(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即可。 } } },