看完這篇文章,就不用操心跨域問題啦,答案都在這裡!
- 2019 年 10 月 7 日
- 筆記
以前工作開發中,經常會有這樣的問題,前端工程師的前端頁面由於跨域問題報錯了,來協調後端開發人員解決,後台開發人員還那解釋你來看我這邊的介面是正常的,應該是你的問題,這是前端開發人員的心頓時是崩塌的,如果你還不知道怎麼辦的時候,也許會默默的自己去尋找解決方案,一查解決方案,這個工作應該需要前後台一起配合,你還得給後端開發人員去好說歹說,讓他們也看看一起解決。我很能理解作為前端的我們真是不容易啊。

關於跨域這個問題,不僅前端工程師需要了解,後端工程師也需要了解更應該重視,因為後面會提及到相關的解決方案,需要共同配合才能完成。借著回答這個問題的機會,我來把跨域的相關內容進行系統的梳理,分享給大家。
什麼是跨域

跨域(CORS)——跨源資源共享。換成我們前端開發人員能理解的就是指瀏覽器不能執行其他網站的腳本。它是由瀏覽器的同源策略造成的,是瀏覽器對JavaScript的實施的安全限制。
換個通俗的比方來說,比如經常會有一些模仿金融機構的釣魚網站,用了和金融機構類似的域名,你點擊進去一看,竟然和你熟悉的金融網站一模一樣,如果你沒注意域名的差別,如果你在網站上輸入了卡號和密碼資訊那就很危險了,有可能這個網站是frame了金融機構的網站,如果金融網站沒有做相關的安全限制,你的資訊完全有可能被非法分子獲取。由此可見瀏覽器的同源策略存在是十分有必要的。
我順便在給大家介紹下如何區分是否是同源,所謂的同源是指,域名,協議,埠均為相同。接下來舉幾個示例,方便大家進行理解:

常用方法一:使用 JSONP 進行 Get 請求
這應該是我們接觸到的第一個解決跨域的方法,筆者記得前端入門經典紅皮書里有過介紹,JSONP有兩部分主成:回調函數和數據。回調函數是當響應完成在頁面中調用的函數,回調函數的名字一般在請求中進行制定。而數據就是傳入回調函調函數中的JSON數據。為了解釋這個,還是我們來看下面這個例子吧:
比如我們來實現一個獲取當地天氣數據的功能,我們需要在後端與天氣介面平台交互獲取天氣數據,前端頁面通過GET後端API的方式獲取天氣資訊。
1、首先定義我們前端頁面的回調函數功能,我們定義了一個gotWeather的函數:

2、接下來定義請求方法,請注意callback後面的參數和回調函數保持一致的名字:

3、我們後台介面最終要返回非類似這樣的數據內容:

你會發現,數據能夠正常返回,你也許會問為什麼這樣可以,不違背同源原則嗎?其實之所以有效,並且不違反安全性,因為這是經過前後端共同協作,約定以這種方式傳遞數據。但是你會發現使用這種方法會有一個問題是,只能用於Get請求。
常用方法二:跨域資源共享(CORS)請求方式
目前這種方式用的比較多,應用比較廣泛,如果你的項目受部署環境限制的話,建議還是用這種。

1、什麼是CORS?
CORS是一個W3C標準,全稱是「跨域資源共享」(跨源資源共享)。它允許瀏覽器向跨源伺服器,發出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。
CORS需要瀏覽器和伺服器同時支援目前,所有瀏覽器都支援該功能,IE瀏覽器不能低於IE10.IE8 +:IE8 / 9需要使用XDomainRequest對象來支援CORS。
整個CORS通訊過程,都是瀏覽器自動完成,不需要用戶參與。對於開發者來說,CORS通訊與同源的AJAX通訊沒有差別,程式碼完全一樣。瀏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭資訊,有時還會多出一次附加的請求,但用戶不會有感覺。因此,實現CORS通訊的關鍵是伺服器。只要伺服器實現了CORS介面,就可以跨源通訊。
CORS 請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。
2、什麼是簡單請求?
2.1、首先介紹下什麼是簡單請求,請求方法是以下請求方法:
- Head
- Get
- Post
2.2、HTTP 的頭資訊不超出以下幾種欄位:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain
凡是不同時滿足上面兩個條件,就屬於非簡單請求。一句話,簡單請求就是簡單的 HTTP 方法與簡單的 HTTP 頭資訊的結合。
2.3、簡單請求的大致流程我做下解釋:
加入我們的一個網站頁面地址需要去請求一個服務端的API,這個頁面的請求頭可能是這樣的:

上面的頭資訊中,Origin欄位用來說明,本次請求來自哪個域(協議 + 域名 + 埠)。伺服器根據這個值,決定是否同意這次請求。
如果Origin指定的源,不在許可範圍內,伺服器會返回一個正常的 HTTP 回應。瀏覽器發現,這個回應的頭資訊沒有包含Access-Control-Allow-Origin欄位,就知道出錯了,從而拋出一個錯誤,被XMLHttpRequest的onerror回調函數捕獲。注意,這種錯誤無法通過狀態碼識別,因為 HTTP 回應的狀態碼有可能是200。
如果Origin指定的域名在許可範圍內,伺服器返回的響應,會多出幾個頭資訊欄位。具體的請求交互流程如下圖所示:

如果一切順利正常的話,你就會看到服務端一些返回的頭資訊

3、什麼是非簡單請求
3.1、 簡單的介紹下什麼是非簡單請求(not-so-simple request)
非簡單請求是那種對伺服器提出特殊要求的請求,比如請求方法是PUT或DELETE,或者Content-Type欄位的類型是application/json。
非簡單請求的 CORS 請求,會在正式通訊之前,增加一次 HTTP 查詢請求,稱為「預檢」請求(preflight)。瀏覽器先詢問伺服器,當前網頁所在的域名是否在伺服器的許可名單之中,以及可以使用哪些 HTTP 動詞和頭資訊欄位。只有得到肯定答覆,瀏覽器才會發出正式的XMLHttpRequest請求,否則就報錯。這是為了防止這些新增的請求,對傳統的沒有 CORS 支援的伺服器形成壓力,給伺服器一個提前拒絕的機會,這樣可以防止伺服器大量收到DELETE和PUT請求,這些傳統的表單不可能跨域發出的請求。
3.2、通過示例,我們來了解其實現的原理
3.2.1、比如我們在前端頁面的請求程式碼時這樣的如下所示:

上面程式碼中,HTTP 請求的方法是PUT,並且發送一個自定義頭資訊X-Custom-Header。
3.2.2、瀏覽器發現,這是一個非簡單請求,就自動發出一個「預檢」請求,要求伺服器確認可以這樣請求。下面是這個「預檢」請求的 HTTP 頭資訊。

「預檢」請求用的請求方法是OPTIONS,表示這個請求是用來詢問的。頭資訊裡面,關鍵欄位是Origin,表示請求來自哪個源。
除了Origin欄位,「預檢」請求的頭資訊包括兩個特殊欄位。
(1)Access-Control-Request-Method 該欄位是必須的,用來列出瀏覽器的 CORS 請求會用到哪些 HTTP 方法,上例是PUT。
(2)Access-Control-Request-Headers 該欄位是一個逗號分隔的字元串,指定瀏覽器 CORS 請求會額外發送的頭資訊欄位,上例是X-Custom-Header。
3.3、伺服器收到「預檢」請求以後,檢查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers欄位以後,確認允許跨源請求,就可以做出回應。

3.4、一旦伺服器通過了「預檢」請求,以後每次瀏覽器正常的 CORS 請求,就都跟簡單請求一樣,會有一個Origin頭資訊欄位。伺服器的回應,也都會有一個Access-Control-Allow-Origin頭資訊欄位。
3.5、 文字內容有點多,把剛才描述的內容用一張流程圖表述下,大家會清晰許多,如下所示:

4、與 JSONP 的比較
CORS 與 JSONP 的使用目的相同,但是比 JSONP 更強大。JSONP 只支援GET請求,CORS 支援所有類型的 HTTP 請求。JSONP 的優勢在於支援老式瀏覽器,以及可以向不支援 CORS 的網站請求數據。
5、接下來給後端開發人員分享下如何配置跨域請求
5.1、 PHP 簡單示例

5.2、 Node 開發人員使用 Express 簡單示例:
5.2.1 首先安裝 cors 中間件:
npm install cors
5.2.2 然後配置比如入口文件,server/
index.js

5.2.3 你可以對跨域進行配置,如下圖所示:

5.2.4 你可以做個請求示例嘗試下,如果一切正常,你可以在 web 開發者工具中看到如下所示:

java 的由於我不太熟,可以自行解決方案,原理和 PHP 的道理是差不多的。
常用方法三:nginx 反向代理
這個方法應用也十分廣泛,也是十分常見的,這也需要服務端配合下面還是用一段Ngxin配置來說明這個問題,如下圖所示:

實現原理類似於Node中間件代理,需要你搭建一個中轉nginx伺服器,用於轉發請求。使用nginx反向代理實現跨域,是最簡單的跨域方式。只需要修改nginx的配置即可解決跨域問題,支援所有瀏覽器,支援session,不需要修改任何程式碼,並且不會影響伺服器性能。實現思路:通過nginx配置一個代理伺服器(域名與domain1相同,埠不同)做跳板機,反向代理訪問domain2介面,並且可以順便修改cookie中domain資訊,方便當前域cookie寫入,實現跨域登錄。
小節
以上是解決跨域問題最常用的三種方式,應該能解決你業務中遇到的問題,有點需需要提示的是方法二和方法三不要混著用,否則會報「Access-Control-Allow-Origin Not Allow Multiple value」的錯誤,我推薦大家用方法三:使用nginx反向代理做跨域解決方案,比較簡單和直接,可謂一勞永逸。當然跨域的方法還有其他的,比如使用WebSocket、postMessage API 、各種 iframe 的解決方案,由於不太常用和篇幅問題原因,就不再一一介紹了,感興趣的小夥伴們可以自行搜索。
註:本回答第二部部分參考阮一峰的《JavaScript 標準參考教程(alpha)》