01、爲什麼要跨域?
跨域的根本原因是瀏覽器的“同源策略”,得先了解什麼是同源?—— 就是【協議+域名+端口號】相同,即爲同源,只能向同源的服務發起AJAX請求。
源1 | 源2 | 是否同源 |
---|---|---|
a.com | b.com | 🚫不同源,域名不同 |
http://a.com | https://a.com | 🚫不同源,協議不同 |
a.com:80 | a.com:443 | 🚫不同源,端口不同 |
gg.com | a.gg.com | 🚫不同源,子域名不同 |
a.com/ss | a.com/s2 | 同源 |
可通過
location.origin
、window.origin
獲取當前文檔的源
❓爲什麼要同源呢?
這是瀏覽器故意設計的,是瀏覽器的基本安全策略,否則會很容易受到XSS、CSRF攻擊。只能向同源的服務發起AJAX請求,不可跨域請求,會被瀏覽器攔截。
❓有哪些限制規則呢?
- ✅ 訪問其他源的圖片、CSS、JS是可以的,允許
<img src="url">
、<link href="url">
、<script src="url">
元素獲取的其他源的資源。 - ✅ Form表單可以跨域提交,表單的提交只是提交數據無需返回,瀏覽器認爲是安全的。
- 🚫 AJAX不可以向其他源發送網絡請求,會被瀏覽器攔截。注意攔截的不是請求,而是響應,服務端依然是可以收到請求的。
- 🚫 僅可訪問自己域的cookie、localStorage、DOM樹,不能訪問Iframe嵌入的其他頁面內部內容。
02、如何實現跨域?
隨着互聯網越來越複雜,需求也越來越多,跨域請求就很常見了。我們知道了跨域是瀏覽器的同源限制,就可以針對性的想辦法了。
2.1、JSONP跨域
這是一種傳統的跨域請求辦法,藉助於<script>
標籤元素,因爲<script>
的src
可以訪問任何站點的資源。當然這需要服務端對應接口支持JSONP(JSON with padding)協議,所以是需要雙方約定好,所以瀏覽器認爲這是安全的。
- 優點是兼任IE,實現跨域。
- 缺點是不能控制請求過程,僅支持GET方式請求。因爲只是一個
<script>
標籤,瀏覽器自動發起的資源請求。
JSONP(JSON with Padding)是JSON的一種”使用模式“,是一種非官方的協議,用於解決瀏覽器的跨域數據訪問的問題。
📢前端具體實現過程:
- 1、申明一個全局的回調函數“getData”來接收數據。
- 2、動態創建一個
<script>
標籤,src
爲要跨域的API地址,URL中帶上回調參數“callback=getData”。 - 3、服務端收到請求後,動態生成一個腳本,腳本內容是一個字符串,由回調+返回的數據構成:“
getData('data')
”。 - 4、本地執行遠程腳本,回調函數“getData”運行,就得到了想要的數據。
<script src="http:www.thrid.com/cors/api?q=key&callback=back"></script>
<script>
function back(data) {
console.log(data);
}
</script>
JSONP的實現:
function jsonp(url, args, cbName) {
return new Promise((resolve, reject) => {
const ele = document.createElement('script');
window[cbName] = (data) => {
resolve(data);
document.body.removeChild(ele);
}
args = { ...args, callback: cbName };
ele.src = `${url}?${Object.keys(args).map(k => `${k}=${args[k]}`).join('&')}`;
document.body.appendChild(ele);
});
}
//使用,api爲360的公開接口
jsonp('https://sug.so.360.cn/suggest', { format: 'jsonp', word: 'china' }, 'search')
.then(function (data) {
console.log(data)
});
2.2、CORS跨域
CORS是什麼?—— 跨域資源共享 (cross-origin resource sharing),讓AJAX可以跨域訪問數據。這是爲了滿足跨域請求的需求,W3C新增加的特性,需要服務端的支持,不支持IE8/9。根據請求方式,瀏覽器將CORS分爲兩種情況:
- 簡單請求(安全請求):只支持GET、POST、HEAD,Header只支持部分字段。
- 複雜請求(其他請求):簡單請求以外的其他跨域請求。
🔵簡單請求
基本原理就是在請求頭加入一個身份來源標識,服務端根據這個標識來判等是否允許訪問,如果允許則給一個允許的標記並返回響應。
- 只支持GET、POST、HEAD。
- header —— 我們僅能設置基礎的安全字段:
- Accept
- Accept-Language
- Content-Language
- Content-Type 的值爲 application/x-www-form-urlencoded,multipart/form-data 或 text/plain。
📢具體過程比較簡單,前端只要在Header加入“Origin”即可:
- 請求頭Header加入要跨域的源:
origin:http://www.main.com
GET /api HTTP/1.1
Origin: http://www.main.com //本次請求來自哪個源
Host: http://www.third.com //請求的第三方API
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0
...
- 服務端收到請求後檢查Origin,如果同意請求則正常響應,同時在響應的Header中加入特殊的“Access-Control-Allow-Origin”字段,申明支持的源,也可以用“*”表示支持任何源訪問。
- 瀏覽器收到響應後會檢查“Access-Control-Allow-Origin”,和當前源對比,如果不合法則會報錯——跨域。
Access-Control-Allow-Origin: http://www.main.com //請求允許的源
Access-Control-Allow-Credentials: true //是否允許cookie,cors默認不發送cookie,如果要發送,還需AJAX中設置withCredentials
Access-Control-Expose-Headers: Content-Length,API-Key //如果客戶端想要訪問其他非安全字段,則需要服務端明確定義哪些Header字段暴露出來
Content-Type: text/html; charset=utf-8
🟠複雜請求
不是簡單請求的都稱爲複雜請求(非簡單請求),如請求方法是PUT、DELETE,或Content-Type=application/json
。相比於簡單請求,複雜請求多了一次預請求。
預請求:
- 正式發送請求前,瀏覽器會自動發送一個預請求,問問服務端是否允許本次請求,如果迴應允許才正式發送請求,後面就和簡單請求相同了。
- 預請求及其響應都沒有body,採用
OPTIONS
方法。
03、跨域小結
因爲同源是瀏覽器的限制,跨域的方法無非就是繞過,或採用CORS。
跨域方案 | 基本原理 | 是否需要服務端支持 |
---|---|---|
JSONP | 藉助<script> 標籤的src ,加上一個全局回調函數接收數據 |
🟠需要服務端支持JSONP協議 |
CORS | W3C標準支持的跨域方式,請求頭添加Origin 字段 |
🟠需要服務端支持 |
WebSocket | WebSocket可以實現瀏覽器與服務端的雙向通信,沒有跨域的困惑。推薦第三方庫 Socket.io,可以很方便的建立與服務端的Socket通信。 | 🟠需要服務端支持,支持WebSocket |
iframe+postMessage | 使用window.postMessage() 來實現窗口之間的通信 |
🔵不需服務端處理,客戶端繞過 |
服務端代理 | 由自己的同源服務端代理第三方的請求 | 🟠需要服務端支持,代理請求 |
nginx反向代理 | 原理和服務端代理一樣,用nginx配置一個代理服務 | 🔵不需要服務端修改代碼,需nginx支持 |
參考資料
©️版權申明:版權所有@安木夕,本文內容僅供學習,歡迎指正、交流,轉載請註明出處!原文編輯地址-語雀