前後端分離項目,如何解決跨域問題?

別看了小姐


前後端分離項目跨域問題是不可避免的。通常情況下前端由React、Vue等框架編寫,通過ajax請求服務端API,傳輸數據用json格式。

那麼為什麼有跨域的問題呢?解決跨域問題有哪些方式?搞清楚這兩個問題我們需要了解一下什麼是同源策略。

瀏覽器的同源策略

同源策略(Same origin policy)是一種安全約定,是所有主流瀏覽器最核心也是最基本的安全功能之一。同源策略規定:不同域的客戶端腳本在沒有明確授權的情況下,不能請求對方的資源。同源指的是:域名、協議、端口均相同。

比如我們訪問一個網站

http://www.test.com/index.html,

那麼這個頁面請求如下地址得情況是這樣的:

另外,同源策略又分如下兩種情況:

  1. DOM同源策略:禁止對不同源的頁面DOM進行操作,主要防止iframe的情況。比如iframe標籤裡放一個支付寶付款的頁面,如果沒有同源策略,那麼釣魚網站除了域名不同,其他的則可以和支付寶的網站一模一樣。

  2. XMLHttpRequest同源策略:禁止使用XHR對象向不同源的服務器發起http請求。比如網站記錄了銀行的cookie,這個時候你訪問了惡意網站,黑客拿到你的cookie,再通過ajax請求之前的銀行網站,便可以輕易的拿到你的銀行信息。

所以,正是因為有了同源策略,大家的網絡環境才相對的安全一些。

跨域問題的解決辦法

瞭解了同源策略,就知道為什麼會有跨域問題的產生了,都是為了安全。但是實際研發中,大家還是需要跨域去訪問資源。典型的應用場景就是前後端分離的項目了。那麼我們如何去解決跨域問題呢?

CORS-跨域資源共享

CORS是一種W3C標準,定義了當產生跨域問題的時候,客戶端與服務端如何通信解決跨域問題。實際上就是前後端約定好定義一些自定義的http請求頭,讓客戶端發起請求的時候能夠讓服務端識別出來該請求是過還是不過。

瀏覽器將CORS請求分為簡單請求和非簡單請求:

簡單請求

簡單請求必須滿足以下兩個條件:

  1. 請求方式必須是HEAD、GET、POST三種方法之一。

  2. Http請求頭必須只能是:Accept、Accept-Lanuage、Content-Lanuage、Last-Event-ID、Content-Type,其中Content-Type只限於三個值 application/x-www-form-urlencoded、multipart/form-data、text/plain。

非簡單請求

不滿足簡單請求條件的就是非簡單請求。針對非簡單請求,瀏覽器會發起預檢請求。預檢請求的意思是當瀏覽器檢查到你的頁面含有跨域請求的時候,會發送一個OPTIONS請求給對應的服務器,以檢測服務器是否允許當前域名的跨域請求。如果服務端允許該域名請求,則返回204或200狀態碼,瀏覽器接收到允許請求時候再繼續發送對應的GET/POST/PUT/DELETE請求。同時服務器端也會告知瀏覽器預檢請求的緩存時長是多少,在這個時間範圍內,瀏覽器不會再次發起預檢請求。


原理基本上就是上面說的這些,實際業務中我們如何通過配置來解決跨域問題呢?基本上常見的就是三種方式:

nginx配置

通常我們在nginx增加如下配置即可解決跨域問題:

用nginx這種方式是最舒服的,不需要客戶端和服務端多做其他工作,對代碼無入侵。

jsonp

因為script標籤是不受瀏覽器同源策略的影響,允許跨域請求資源(我們的每一個頁面都引用了大量第三方js文件)。所以可以利用動態創建script標籤,通過src屬性發起跨域請求,這就是jsonp的原理。但是jsonp只支持GET請求,所以並不是一種好的方式。

服務端代碼控制

可以在服務端增加對跨域請求的支持:

這種方式相當於全局過濾器,對所有請求都過濾一遍。

以上三種方式都可以一定程度上解決跨域問題,但是nginx配置和服務端控制不能同時存在,否則會報“Access-Control-Allow-Origin Not Allow Multiple value”的錯誤。個人比較推薦nginx配置的方式,一勞永逸,不需要每個web項目都去編寫跨域的代碼。

大家在工作中有沒有遇到過跨域問題呢?都是怎麼解決的?歡迎評論區交流討論,共同學習~


java架構設計


現在開發項目,大部分公司採用的都是前後端分離的方式進行開發,由於現在產品形態越來越多,網頁、手機端、桌面端等等,為了面對各種端,數據中心化、微服務概念的出現,我們為了集成這些服務,不得不去面對一個常見的問題——解決跨域請求的問題。


以前工作開發中,經常會有這樣的問題,前端工程師的前端頁面由於跨域問題報錯了,來協調後端開發人員解決,後臺開發人員還那解釋你來看我這邊的接口是正常的,應該是你的問題,這是前端開發人員的心頓時是崩塌的,如果你還不知道怎麼辦的時候,也許會默默的自己去尋找解決方案,一查解決方案,這個工作應該需要前後臺一起配合,你還得給後端開發人員去好說歹說,讓他們也看看一起解決。我很能理解作為前端的我們真是不容易啊。


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


什麼是跨域


跨域(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.2、簡單請求的大致流程我做下解釋:

加入我們的一個網站頁面地址需要去請求一個服務端的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.4 文字內容有點多,把剛才描述的內容用一張流程圖表述下,大家會清晰許多,如下所示:


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.3 然後配置比如入口文件,server/

index.js


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


5.2.5 你可以做個請求示例嘗試下,如果一切正常,你可以在 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)》



前端達人


1.首先明白跨域是怎麼導致,跨域是由於瀏覽器的同源策略導致 ,什麼是同源策略,指的不同協議 或者不同域名 或者不同端口 只要是其中一個不同 都會導致跨域問題

2.怎麼解決 只要通過配置請求頭部信息 放行所有請求域名 以及請求方法


分享到:


相關文章: