請求跨域問題詳解

  在JavaScript中,有一個很重要的安全性限制,被稱爲「Same-Origin Policy」(同源策略)。這一策略對於JavaScript代碼可以訪問的頁面內容作了很重要的限制,即JavaScript只能訪問與包含它的文檔在同一域下的內容。javascript

  💢跨越是瀏覽器進行安全限制的一種方法,若是瀏覽器禁用了這種安全限制就不會出現跨域問題產生跨域的緣由(如下三者都知足):css

  • 只要調用方訪問被調用方的域名、端口、IP不同
  • 瀏覽器沒有禁用安全限制
  • 採用了XMLHttpRequest的請求

  JavaScript這個安全策略在進行多iframe或多窗口編程、以及Ajax編程時顯得尤其重要。根據這個策略,在baidu.com下的頁面中包含的JavaScript代碼,不能訪問在google.com域名下的頁面內容;甚至不一樣的子域名之間的頁面也不能經過JavaScript代碼互相訪問。對於Ajax的影響在於,經過XMLHttpRequest實現的Ajax請求,不能向不一樣的域提交請求,例如,在abc.example.com下的頁面,不能向def.example.com提交Ajax請求,等等。html

  爲何瀏覽器要實現同源限制?咱們舉例說明:前端

  好比一個黑客,他利用iframe把真正的銀行登陸頁面嵌到他的頁面上,當你使用真實的用戶名和密碼登陸時,若是沒有同源限制,他的頁面就能夠經過javascript讀取到你的表單中輸入的內容,這樣用戶名和密碼就輕鬆到手了。又好比你登陸了OSC,同時瀏覽了惡意網站,若是沒有同源限制,該惡意網站就能夠構造AJAX請求頻繁在OSC發廣告帖。java

1、跨域問題發生場景

  

  • 特別注意兩點:

  一、若是是協議和端口形成的跨域問題「前臺」是無能爲力的nginx

  二、在跨域問題上,域僅僅是經過「URL的首部」來識別而不會去嘗試判斷相同的ip地址對應着兩個域或兩個域是否在同一個ip上。好比上面的,http://www.a.com/a.js和http://70.32.92.74/b.js。雖然域名和域名的ip對應,不過仍是被認爲是跨域。ajax

  *「URL的首部」指window.location.protocol +window.location.host。其中,window.location.protocol:指含有URL第一部分的字符串,如http: ,window.location.host:指包含有URL中主機名:端口號部分的字符串.如//www.cenpok.net/server/編程

2、跨域問題的解決方案

  💢解決跨域的思路json

  • 被調用方解決: 被調用方解決-支持跨域(根據http協議關於跨域方面的要求,增長響應頭信息,告訴瀏覽器容許被跨域調用)(由於在發生跨域請求時首先調用方發送一個預檢請求(OPTIONS請求),這個請求就會被帶上容許跨越的請求頭信息)
  • 調用方解決:使用代理作調用解決跨域問題-隱藏跨域(利用nginx的反向代理,使訪問同一個域名不一樣的資源路徑會代理到不一樣的服務器上,每一個跨域的請求都會帶上origin請求頭字段,由於訪問的資源都是同域名下的,因此不會產生跨越問題)

一、JSONP跨域後端

​   JSONP(JSON with Padding)是數據格式JSON的一種「使用模式」,可讓網頁從別的網域要數據。根據 XmlHttpRequest 對象受到同源策略的影響,而利用 <script>元素的這個開放策略,網頁能夠獲得從其餘來源動態產生的JSON數據,而這種使用模式就是所謂的 JSONP。用JSONP抓到的數據並非JSON,而是任意的JavaScript,用 JavaScript解釋器運行而不是用JSON解析器解析。全部,經過Chrome查看全部JSONP發送的Get請求都是js類型,而非XHR。 

    

①原理

  咱們知道,在頁面上有三種資源是能夠與頁面自己不一樣源的。它們是:js腳本,css樣式文件,圖片,像淘寶等大型網站,確定會將這些靜態資源放入cdn中,而後在頁面上鍊接,以下所示,因此它們是能夠連接訪問到不一樣源的資源的。

1 <script type="text/javascript" src="某某cdn地址" ></script>
2 <link type="text/css" rel="stylesheet" href="某個cdn地址" />
3 <img src="某個cdn地址" alt=""/>

  而jsonp就是利用了script標籤的src屬性是沒有跨域的限制的,從而達到跨域訪問的目的。所以它的最基本原理就是:動態添加一個<script>標籤來實現。

②實現方法:

  這裏是使用ajax來請求的,看起來和ajax沒啥區別,其實仍是有區別的。ajax的核心是經過XmlHttpRequest獲取非本頁內容,而jsonp的核心則是動態添加<script>標籤來調用服務器提供的js腳本。

複製代碼
$.ajax({  
        url:"http://www.baidu.com/service",  
        dataType:'jsonp',  
        data:'',  
        jsonp:'callback',  
        success:function(data) {  
            // some code
        }  
    });  
複製代碼

  上面的代碼中,callback是必須的,callback是什麼值要跟後臺拿。獲取到的jsonp數據格式以下:

callback({
    "code": "CA1998",
    "price": 1780,
    "tickets": 5
});

 ③JSONP的不足之處:

  1. 只能使用get方法,不能使用post方法:咱們知道 script,link, img 等等標籤引入外部資源,都是 get 請求的,那麼就決定了 jsonp 必定是 get 的。但有時候咱們使用的 post 請求也成功,爲啥呢?這是由於當咱們指定dataType:'jsonp',不論你指定:type:"post" 或者type:"get",其實質上進行的都是 get 請求!
  2. 沒有關於 JSONP 調用的錯誤處理。若是動態腳本插入有效,就執行調用;若是無效,就靜默失敗。失敗是沒有任何提示的。例如,不能從服務器捕捉到 404 錯誤,也不能取消或從新開始請求。不過,等待一段時間尚未響應的話,就不用理它了。

二、跨域資源共享 CORS

​   Cross-Origin Resource Sharing(CORS)跨域資源共享是一份瀏覽器技術的規範,提供了 Web 服務從不一樣域傳來沙盒腳本的方法,以避開瀏覽器的同源策略,確保安全的跨域數據傳輸。現代瀏覽器使用CORS在API容器如XMLHttpRequest來減小HTTP請求的風險來源。與 JSONP 不一樣,CORS 除了 GET 要求方法之外也支持其餘的 HTTP 要求。

  瀏覽器將CORS請求分紅兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。因爲該類跨域方案使用較爲普遍,本篇將詳細介紹。

2.1簡單請求

  只要同時知足如下兩大條件,就屬於簡單請求。

(1)請求方法是如下三種方法之一:

1 HEAD 2 GET 3 POST

(2)HTTP的頭信息不超出如下幾種字段:

1 Accept 2 Accept-Language 3 Content-Language 4 Last-Event-ID 5 Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain

2.1.1基本流程

  對於簡單請求,瀏覽器直接發出CORS請求。具體來講,就是在頭信息之中,增長一個Origin字段。下面是一個例子,瀏覽器發現此次跨源AJAX請求是簡單請求,就自動在頭信息之中,添加一個Origin字段。

1 GET /cors HTTP/1.1 2 Origin: http://api.bob.com 3 Host: api.alice.com 4 Accept-Language: en-US 5 Connection: keep-alive 6 User-Agent: Mozilla/5.0...

  上面的頭信息中,Origin字段用來講明,本次請求來自哪一個源(協議 + 域名 + 端口)。服務器根據這個值,決定是否贊成此次請求。

  若是Origin指定的源,不在許可範圍內,服務器會返回一個正常的HTTP迴應。瀏覽器發現,這個迴應的頭信息沒有包含Access-Control-Allow-Origin字段(詳見下文),就知道出錯了,從而拋出一個錯誤,被XMLHttpRequestonerror回調函數捕獲。注意,這種錯誤沒法經過狀態碼識別,由於HTTP迴應的狀態碼有多是200。

  若是Origin指定的域名在許可範圍內,服務器返回的響應,會多出幾個頭信息字段。

1 Access-Control-Allow-Origin: http://api.bob.com 2 Access-Control-Allow-Credentials: true 3 Access-Control-Expose-Headers: FooBar 4 Content-Type: text/html; charset=utf-8

  上面的頭信息之中,有三個與CORS請求相關的字段,都以Access-Control-開頭。

(1)Access-Control-Allow-Origin

  該字段是必須的。它的值要麼是請求時Origin字段的值,要麼是一個*,表示接受任意域名的請求。

(2)Access-Control-Allow-Credentials

  該字段可選。它的值是一個布爾值,表示是否容許發送Cookie。默認狀況下,Cookie不包括在CORS請求之中。設爲true,即表示服務器明確許可,Cookie能夠包含在請求中,一塊兒發給服務器。這個值也只能設爲true,若是服務器不要瀏覽器發送Cookie,刪除該字段便可。

(3)Access-Control-Expose-Headers

  該字段可選。CORS請求時,XMLHttpRequest對象的getResponseHeader()方法只能拿到6個基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。若是想拿到其餘字段,就必須在Access-Control-Expose-Headers裏面指定。上面的例子指定,getResponseHeader('FooBar')能夠返回FooBar字段的值。

2.1.2 withCredentials 屬性

  上面說到,CORS請求默認不發送Cookie和HTTP認證信息。若是要把Cookie發到服務器,一方面要服務器贊成,指定Access-Control-Allow-Credentials字段。

1 Access-Control-Allow-Credentials: true

  另外一方面,開發者必須在AJAX請求中打開withCredentials屬性。

1 var xhr = new XMLHttpRequest(); 2 xhr.withCredentials = true;

  不然,即便服務器贊成發送Cookie,瀏覽器也不會發送。或者,服務器要求設置Cookie,瀏覽器也不會處理。可是,若是省略withCredentials設置,有的瀏覽器仍是會一塊兒發送Cookie。這時,能夠顯式關閉withCredentials

1 xhr.withCredentials = false;

  須要注意的是,若是要發送Cookie,Access-Control-Allow-Origin就不能設爲星號,必須指定明確的、與請求網頁一致的域名。同時,Cookie依然遵循同源政策,只有用服務器域名設置的Cookie纔會上傳,其餘域名的Cookie並不會上傳,且(跨源)原網頁代碼中的document.cookie也沒法讀取服務器域名下的Cookie。

2.2非簡單請求

2.2.1 預檢請求

  非簡單請求是那種對服務器有特殊要求的請求,好比請求方法是PUTDELETE,或者Content-Type字段的類型是application/json

  非簡單請求的CORS請求,會在正式通訊以前,增長一次HTTP查詢請求,稱爲"預檢"請求(preflight)。瀏覽器先詢問服務器,當前網頁所在的域名是否在服務器的許可名單之中,以及可使用哪些HTTP動詞和頭信息字段。只有獲得確定答覆,瀏覽器纔會發出正式的XMLHttpRequest請求,不然就報錯。

  下面是一段瀏覽器的JavaScript腳本。

1 var url = 'http://api.alice.com/cors'; 2 var xhr = new XMLHttpRequest(); 3 xhr.open('PUT', url, true); 4 xhr.setRequestHeader('X-Custom-Header', 'value'); 5 xhr.send();

  上面代碼中,HTTP請求的方法是PUT,而且發送一個自定義頭信息X-Custom-Header。瀏覽器發現,這是一個非簡單請求,就自動發出一個"預檢"請求,要求服務器確承認以這樣請求。下面是這個"預檢"請求的HTTP頭信息。

1 OPTIONS /cors HTTP/1.1 2 Origin: http://api.bob.com 3 Access-Control-Request-Method: PUT 4 Access-Control-Request-Headers: X-Custom-Header 5 Host: api.alice.com 6 Accept-Language: en-US 7 Connection: keep-alive 8 User-Agent: Mozilla/5.0...

  "預檢"請求用的請求方法是OPTIONS,表示這個請求是用來詢問的。頭信息裏面,關鍵字段是Origin,表示請求來自哪一個源。除了Origin字段,"預檢"請求的頭信息包括兩個特殊字段。

(1)Access-Control-Request-Method

  該字段是必須的,用來列出瀏覽器的CORS請求會用到哪些HTTP方法,上例是PUT

(2)Access-Control-Request-Headers

  該字段是一個逗號分隔的字符串,指定瀏覽器CORS請求會額外發送的頭信息字段,上例是X-Custom-Header

2.2.2 預檢請求的迴應

  服務器收到"預檢"請求之後,檢查了OriginAccess-Control-Request-MethodAccess-Control-Request-Headers字段之後,確認容許跨源請求,就能夠作出迴應。

 1 HTTP/1.1 200 OK  2 Date: Mon, 01 Dec 2008 01:15:39 GMT  3 Server: Apache/2.0.61 (Unix)  4 Access-Control-Allow-Origin: http://api.bob.com  5 Access-Control-Allow-Methods: GET, POST, PUT  6 Access-Control-Allow-Headers: X-Custom-Header  7 Content-Type: text/html; charset=utf-8  8 Content-Encoding: gzip  9 Content-Length: 0 10 Keep-Alive: timeout=2, max=100 11 Connection: Keep-Alive 12 Content-Type: text/plain

  上面的HTTP迴應中,關鍵的是Access-Control-Allow-Origin字段,表示http://api.bob.com能夠請求數據。該字段也能夠設爲星號,表示贊成任意跨源請求。

1 Access-Control-Allow-Origin: *

  若是瀏覽器否認了"預檢"請求,會返回一個正常的HTTP迴應,可是沒有任何CORS相關的頭信息字段。這時,瀏覽器就會認定,服務器不一樣意預檢請求,所以觸發一個錯誤,被XMLHttpRequest對象的onerror回調函數捕獲。控制檯會打印出以下的報錯信息。

1 XMLHttpRequest cannot load http://api.alice.com. 2 Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.

  服務器迴應的其餘CORS相關字段以下。

1 Access-Control-Allow-Methods: GET, POST, PUT 2 Access-Control-Allow-Headers: X-Custom-Header 3 Access-Control-Allow-Credentials: true 4 Access-Control-Max-Age: 1728000

(1)Access-Control-Allow-Methods

  該字段必需,它的值是逗號分隔的一個字符串,代表服務器支持的全部跨域請求的方法。注意,返回的是全部支持的方法,而不單是瀏覽器請求的那個方法。這是爲了不屢次"預檢"請求。

(2)Access-Control-Allow-Headers

  若是瀏覽器請求包括Access-Control-Request-Headers字段,則Access-Control-Allow-Headers字段是必需的。它也是一個逗號分隔的字符串,代表服務器支持的全部頭信息字段,不限於瀏覽器在"預檢"中請求的字段。

(3)Access-Control-Allow-Credentials

  該字段與簡單請求時的含義相同。

(4)Access-Control-Max-Age

  該字段可選,用來指定本次預檢請求的有效期,單位爲秒。上面結果中,有效期是20天(1728000秒),即容許緩存該條迴應1728000秒(即20天),在此期間,不用發出另外一條預檢請求。

2.2.3 瀏覽器的正常請求和迴應

  一旦服務器經過了"預檢"請求,之後每次瀏覽器正常的CORS請求,就都跟簡單請求同樣,會有一個Origin頭信息字段。服務器的迴應,也都會有一個Access-Control-Allow-Origin頭信息字段。下面是"預檢"請求以後,瀏覽器的正常CORS請求。

1 PUT /cors HTTP/1.1 2 Origin: http://api.bob.com 3 Host: api.alice.com 4 X-Custom-Header: value 5 Accept-Language: en-US 6 Connection: keep-alive 7 User-Agent: Mozilla/5.0...

  上面頭信息的Origin字段是瀏覽器自動添加的。下面是服務器正常的迴應。

1 Access-Control-Allow-Origin: http://api.bob.com 2 Content-Type: text/html; charset=utf-8

  上面頭信息中,Access-Control-Allow-Origin字段是每次迴應都一定包含的。

  CORS實現起來比較簡單,可是缺點是支持瀏覽器有限。

     

三、反向代理

  想一下,若是咱們請求的時候仍是用前端的域名,而後有個東西幫咱們把這個請求轉發到真正的後端域名上,不就避免跨域了嗎?這時候,Nginx出場了。
Nginx配置

 1 server{  2  # 監聽9099端口  3     listen 9099;  4  # 域名是localhost  5  server_name localhost;  6     #凡是localhost:9099/api這個樣子的,都轉發到真正的服務端地址http://localhost:9871 
 7     location ^~ /api {  8         proxy_pass http://localhost:9871;
 9  } 10 }

  前端就不用幹什麼事情了,除了寫接口,也沒後端什麼事情了

 1 // 請求的時候直接用回前端這邊的域名http://localhost:9099,這就不會跨域,而後Nginx監聽到凡是localhost:9099/api這個樣子的,都轉發到真正的服務端地址http://localhost:9871 
 2 fetch('http://localhost:9099/api/iframePost', {  3   method: 'POST',  4  headers: {  5     'Accept': 'application/json',  6     'Content-Type': 'application/json'
 7  },  8  body: JSON.stringify({  9     msg: 'helloIframePost'
10  }) 11 })

  Nginx轉發的方式彷佛很方便!但這種使用也是看場景的,若是後端接口是一個公共的API,好比一些公共服務獲取天氣什麼的,前端調用的時候總不能讓運維去配置一下Nginx,若是兼容性沒問題(IE 10或者以上),CROS纔是更通用的作法吧。

相關文章
相關標籤/搜索