前端跨域問題的解決方案
文章目錄
1. 造成跨域的原因
跨域是指一個域下的文檔或腳本試圖去請求另一個域下的資源.這裏的跨域是廣義的.
廣義的跨域包括:
- 資源跳轉: 鏈接,重定向,表單提交
- 資源嵌入:
<link>,<script>,<img>,<iframe>
等 DOM 標籤 - 腳本請求: javascript 發起的 Ajax 請求等
而我們常說的跨域是狹義的,是由瀏覽器同源策略引起的一類請求場景.
The same-origin policy is a critical security mechanism that restricts how a document or script loaded from one origin can interact with a resource from another origin. It helps isolate potentially malicious documents, reducing possible attack vectors.
來自 MDN
如果兩個域名的協議,域名,端口都相同,那我們就說這兩個域名是同源的.
2. 同源策略限制些什麼?
- 不能向工作在不同源的服務請求數據,即不能發送 Ajax 請求;
- 無法獲取不同源的 document / cookies 等 BOM 和 DOM,可以說任何有關另一個源的信息都無法得到.
3. 爲什麼會有同源策略
3.1 爲什麼要限制不同源發送請求
假設兩個頁面,a 頁面和 b 頁面.如果沒有任何限制,b 頁面可以向 a 頁面請求任何信息,那如果 a 頁面是個
銀行之類的頁面,那就可以進行轉賬之類的請求.
那既然如此,爲什麼不限制寫,只限制讀?
因爲如果連請求都發送不出去,那就不能做跨域資源共享了.
3.2 爲什麼限制跨域的 DOM 讀取?
如果不加以限制,很容易通過 iframe 僞裝其網站.進而可以獲取用戶的登錄信息等.
4. 跨域的解決方式
4.1 CORS
服務器設置:
ACCESS-CONTROL-ALLOW-ORIGIN: *
只要瀏覽器檢測到響應頭帶上了 CORS,並且允許的源包括了本網站,那麼就不會攔截請求響應。
CORS 分爲"簡單請求"以及"預檢請求":
-
簡單請求
使用下列方法之一:- GET
- POST
- HEAD
以及滿足 Fetch 規範定義了對 CORS 安全的首部字段集合,不得人爲設置該集合之外的其他首部字段。
而且Content-Type
的值僅爲下列三者之一:- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
-
預檢請求
與上述簡單請求不一樣."需預檢的請求"要求必須首先使用OPTIONS
方法發起一個預檢請求到服務器,
以獲知服務器是否允許該實際請求。"預檢請求"的使用,可以避免跨域請求對服務器的用戶數據產生未預期的影響。
當滿足以下任一條件時,即應首先發送預檢請求:- 非簡單請求的方法
- 人爲設置了對 CORS 安全的首部字段集合之外的其他首部字段。
- 非簡單請求的
Content-Type
- 請求中的XMLHttpRequestUpload 對象註冊了任意多個事件監聽器。
- 請求中使用了ReadableStream對象。
另外,還有附帶身份驗證的請求,也就是攜帶 cookies.
這個需要前端在請求實例中設置:
// 將 XMLHttpRequest 的 withCredentials 標誌設置爲 true
const xml = new XMLHttpRequest();
// ...
xml.withCredentials = true;
這時,後端需要作出相應的設置.
來自MDN
4.2 JSONP
JSONP 能夠跨域的原理就是:動態創建 script 標籤,利用 script 標籤的 src 請求沒有跨域的限制.
代碼示例
function updateList (data) {
console.log(data);
}
let tag = document.createElement("script")
tag.src = "http://otherdomain.com/request?callback=updateList";
document.head.appendChild(tag);
代碼定義一個全局函數,然後把這個函數名添加到 script 標籤中 src 屬性的參數 callback 中,
script 的 src 就是需要跨域的請求.服務端收到請求之後,將數據放入 callback 屬性的屬性值中:
updateList("somedata")
,然後返回給客戶端:
// script 響應返回的js內容爲
updateList([{
name: 'hello'
}]);
也就是客戶端執行 傳遞的方法 updateList
,函數的參數即是本次跨域請求返回的數據.