理性分析 AJAX 跨域問題

haloislet發表於2018-03-02

跨域(Cross origin)問題是前端中比較常見的問題。 要解決跨域問題,我們需要明白是什麼導致跨域問題的?


同源策略

跨域問題源於瀏覽器的安全策略。為了保護資料的安全性和完整性,瀏覽器使用了同源策略(Same origin policy),以防止惡意指令碼從當前源獲取其他源的敏感資料。

需要注意的是,瀏覽器並不限制當前源傳送資訊到其他源,而是限制接收其他源的資料。

同源策略是瀏覽器的安全策略。使用非瀏覽器程式就沒有這個限制。例如,我們使用 postman 來測試 API 就沒有跨域問題。使用 node.js 程式來訪問介面也沒有跨域問題。

同源策略規定了源必須相同。所謂的源,就是 url 檔案路徑之前的部分。例如:

https://www.baidu.com/index.html
複製程式碼

在 index.html 之前的部分就稱為源。源包括三個部分:協議,域名,埠。

在上述的 url 中, https 表示協議, www.baidu.com 表示域名,還有一個隱藏的埠號 443

http 和 https 都有對應的預設埠號,http 的預設埠為 80,https 對應的埠號為 443。如果想要訪問其他的埠號,就需要在輸入的時候顯示輸入了。

關於源,有一點要注意的是:源中的域名指的是狹義上的域名,三級域名和二級域名也算是不同域名。例如:www.baidu.combaidu.com算是不同的域名。

簡而言之,https://www.baidu.com這個源只有 https://www.baidu.com:443是和它算是同源的,其它一個字元都不能變,對應的 ip 也算是不同源。

同源策略的限制

同源策略有兩個要點:同源、限制指令碼。

javascript 是指令碼語言之一。受同源策略限制, javascript 無法獲取其他源的 DOM ,Cookie、LocalStorage,IndexDB,並且 AJAX 請求無法正常獲取資料。


跨域

有時候我們需要從一個源訪問另外一個源的資料。比如前後端分離之後,前後端程式分別佈置到兩臺伺服器上,這個時候就出現了跨域問題。

跨域(Cross origin),從英文上來看,其實就是跨源。要解決跨域問題,本質上是繞過同源策略。

解決 AJAX 跨域問題,有兩個解決方案:反向代理(從源伺服器著手),CORS(從目標伺服器著手)。

反向代理

反向代理可以在不改動前後端程式的情況下解決跨域問題。

先來了解下什麼是代理(Proxy )。

日常生活中,我們有時會使用 VPN 來科學上網。這個時候會用到代理。我們所有的請求都將由這個代理伺服器轉發,然後返回給客戶端。代理起到了請求轉發的作用。

正向代理和反向代理

在使用 VPN 上網的時候,使用者選擇要訪問的網站,將請求傳送給代理伺服器。代理伺服器將請求轉發到網站伺服器,並將獲取的結果返回給使用者。這個過程中,請求目標是由使用者決定的,代理伺服器事先並不知道訪問的目標伺服器。這就是正向代理。

反向代理與正向代理相反,客戶端向當前伺服器發起請求,當前伺服器將決定將請求轉發到哪個目標伺服器,這個目標伺服器是由當前伺服器配置的。這就是反向代理。

簡而言之,正向代理和反向代理的區別就是正向代理的請求目標是由客戶端決定的,反向代理的請求目標是由代理伺服器決定的。

通過反向代理解決跨域問題

通過反向代理,我們就可以繞過同源策略了。因為我們的所有請求都是傳送到反向代理伺服器中,然後由反向代理伺服器去請求目標伺服器。反向代理伺服器不受瀏覽器同源策略影響。這樣解決了跨域問題。

nginx ,apache 等伺服器都可以配置反向代理。

例如當前伺服器為 http://localhost:80 想要把路徑 / 下的請求轉發到 http://localhost:8080 下。

在 nginx 中可以新增如下配置

location /
{
	proxy_pass http://localhost:8080;
}
複製程式碼

CORS

CORS 全稱 Cross-origin resource sharing (跨域資源共享),要求 IE 版本不能低於 10。

既然瀏覽器同源策略是防止惡意指令碼從當前源獲取其它源的敏感資料,那麼要是其它源想要主動對外開放部分資料,應該怎麼辦呢?

這時候 W3C 提供了一套標準,也就是 CORS。通過 CORS ,伺服器可以選擇性地開放部分資料。

最簡單的配置就是在源伺服器中配置響應頭 Access-Control-Allow-Origin 選項,也就是訪問控制允許的源。可以將其設定為 *,這樣所有的源都可以獲取該伺服器的資料了。

當然了,CORS 標準中規定了一些安全設定。比如將請求分為簡單請求和非簡單請求。超出簡單請求的範圍的話,就需要額外的配置。

簡單請求

簡單請求的限定範圍如下:

請求方法是以下 3 種方法之一:

1. HEAD
2. GET
3. POST

 HTTP的頭資訊不超出以下 5 種欄位:
 
1. Accept
2. Accept-Language
3. Content-Language
4. Last-Event-ID
5. Content-Type:只限於 3 個值application/x-www-form-urlencoded、multipart/form-data、text/plain
複製程式碼

當傳送簡單請求時,瀏覽器會自動在請求頭中加入 Origin 欄位,它的值為當前源。目標伺服器在接收到請求之後,如配置了 Access-Control-Allow-Origin 欄位,就會新增 Access-Control-Allow-Origin 到響應頭中。

當瀏覽器接收到響應時,會檢測 Access-Control-Allow-Origin 的值。

  • 如不存在會丟擲 No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8010' is therefore not allowed access.錯誤。
  • 如不匹配會丟擲 The 'Access-Control-Allow-Origin' header has a value 'http://localhost:8010' that is not equal to the supplied origin.錯誤。
  • 匹配成功會則正常解析響應。

非簡單請求

超過簡單請求的範圍的就是非簡單請求了。非簡單請求需要預檢。在使用非簡單請求的時候,瀏覽器會首先傳送一次預檢請求,以確認當前請求方法和請求頭是否被允許,如被允許再傳送一個請求來獲取返回資料。

例如想要使用 delete 請求。這個時候,需要在目標伺服器上配置 Access-Control-Request-Method ,也就是訪問控制允許的請求方法,在裡面新增 delete 請求。

同理,超出範圍的請求頭需要在目標伺服器配置 Access-Control-Allow-Headers 。

cookie

CORS 預設是不傳送 Cookie 資訊的。這個時候,如果我們想要傳送Cookie 的話,就必須同時在請求和目標伺服器中配置。使用 xhr 傳送 AJAX 請求的話,需要做如下配置:

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
複製程式碼

並且在目標伺服器中配置 Access-Control-Allow-Credentials 欄位。

Access-Control-Allow-Credentials: true
複製程式碼

出於安全的考慮,如要傳送 Cookie 的話,Access-Control-Allow-Origin 就不能設為萬用字元。必須明確指定源。


總結

  1. 跨域問題源於瀏覽器的同源策略。
  2. 同源策略要求協議、域名、埠號必須相同,限制指令碼訪問其他源的資料。
  3. Ajax 跨域訪問有兩種解決方案:反向代理和 CORS 。其中反向代理是在源伺服器中配置,CORS 是在目標伺服器中配置。
    • 反向代理是通過代理伺服器獲取目標伺服器的資料來繞過同源策略。
    • CORS 標準是通過開放目標伺服器訪問許可權來提供資料。

相關知識點

  • JSONP
  • WebSocket

參考連結

相關文章