1. 程式人生 > >瀏覽器和伺服器實現跨域(CORS)判定的原理

瀏覽器和伺服器實現跨域(CORS)判定的原理

同源策略

同源指的是域名(或IP),協議,埠都相同,不同源的客戶端指令碼(javascript、ActionScript)在沒明確授權的情況下,不能讀寫對方的資源。

同源的判定:
http://www.example.com/dir/page.html為例,以下表格指出了不同形式的連結是否與其同源:(原因裡未申明不同的屬性即說明其與例子裡的原連結對應的屬性相同)

連結 結果 原因
http:// www.example.com /dir/page2.html 同協議同域名同埠
http:// www.example.com /dir2/other.html 同協議同域名同埠
http://user:[email protected] www.example.com/dir2/other.html 同協議同域名同埠
http://www.example.com: 81/dir/other.html 埠不同
https://www.example.com/dir/other.html
協議不同埠不同
http:// en.example.com/dir/other.html 域名不同
http:// example.com/dir/other.html 域名不同(要求精確匹配)
http:// v2.www.example.com/dir/other.html 域名不同(要求精確匹配)
http://www.example.com: 80/dir/other.html 不確定
取決於瀏覽器的實現方式

是否允許跨域的判定

前文提到了同源策略的判定,然而同源策略在加強了安全的同時,對開發卻是極大的不便利。因此開發者們又發明了很多辦法來允許資料的跨域傳輸(常見的辦法有JSONPCORS)。當域名不同源的時候,由於跨域實現的存在,瀏覽器不能直接根據域名來判定跨域限制。那麼瀏覽器具體又是如何實現判定的呢?看下面的例子。

環境說明

  1. 參與實驗的前端域名三個有:http://www.zhihu.comhttp://segmentfault.com
    http://localhost

  2. 請求的伺服器端地址為http://localhost/city.json,伺服器解析引擎使用的nginx,且伺服器只配置了允許來自http://segmentfault.com的跨域請求

  3. 檢測方法:在各個域名下利用chrome瀏覽器的console介面模擬傳送ajax請求,程式碼如下:

    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'http://localhost/city.json',true);
    xhr.send();

實驗過程

  1. http://localhost域名下,請求成功。

    伺服器迴應的http檔案頭如下:
    HTTP/1.1 200 OK
    Server: nginx/1.6.2
    Date: Sun, 05 Jul 2015 17:44:06 GMT
    Content-Type: application/octet-stream
    Content-Length: 2084
    Last-Modified: Sat, 18 Apr 2015 06:20:12 GMT
    Connection: keep-alive
    ETag: "5531f79c-824"
    Access-Control-Allow-Credentials: true
    Access-Control-Allow-Methods: GET, POST, OPTIONS
    Accept-Ranges: bytes
  2. http://segmentfault.com域名下,請求成功

    伺服器迴應的http檔案頭如下:
    HTTP/1.1 200 OK
    Server: nginx/1.6.2
    Date: Sun, 05 Jul 2015 18:17:27 GMT
    Content-Type: application/octet-stream
    Content-Length: 2084
    Last-Modified: Sat, 18 Apr 2015 06:20:12 GMT
    Connection: keep-alive
    ETag: "5531f79c-824"
    **Access-Control-Allow-origin: http://segmentfault.com**
    Access-Control-Allow-Credentials: true
    Access-Control-Allow-Methods: GET, POST, OPTIONS
    Accept-Ranges: bytes
  3. http://www.zhihu.com下,請求失敗
    雖然都是失敗,但是返回的HTTP檔案頭內容會視伺服器是否有配置跨域請求而發生變化

伺服器允許跨域請求

(僅允許來自http://segmentfault.com的跨域請求)
console.log視窗提示:

XMLHttpRequest cannot load http://localhost/city.json. The 'Access-Control-Allow-Origin' header has a value 'http://segmentfault.com' that is not equal to the supplied origin. Origin 'http://www.zhihu.com' is therefore notallowed access.

伺服器迴應的http檔案頭如下:

    HTTP/1.1 200 OK
    Server: nginx/1.6.2
    Date: Sun, 05 Jul 2015 17:59:25 GMT
    Content-Type: application/octet-stream
    Content-Length: 2084
    Last-Modified: Sat, 18 Apr 2015 06:20:12 GMT
    Connection: keep-alive
    ETag: "5531f79c-824"
    Access-Control-Allow-origin: http://segmentfault.com
    Access-Control-Allow-Credentials: true
    Access-Control-Allow-Methods: GET, POST, OPTIONS
    Accept-Ranges: bytes

伺服器不允許任何跨域請求

console.log視窗提示:

XMLHttpRequest cannot load http://localhost/city.json. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://www.zhihu.com' is therefore not allowed access.

伺服器迴應的http檔案頭如下:

HTTP/1.1 200 OK
Server: nginx/1.6.2
Date: Sun, 05 Jul 2015 17:51:29 GMT
Content-Type: application/octet-stream
Content-Length: 2084
Last-Modified: Sat, 18 Apr 2015 06:20:12 GMT
Connection: keep-alive
ETag: "5531f79c-824"
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, OPTIONS
Accept-Ranges: bytes

跨域的判定流程

zhihu頁面的兩次瀏覽器報錯以及segmentfault的成功返回值來看,可以很容易得出瀏覽器和伺服器的合作判定步驟如下:

  1. 瀏覽器先根據同源策略對前端頁面和後臺互動地址做匹配,若同源,則直接傳送資料請求;若不同源,則傳送跨域請求。

  2. 伺服器解析程式收到瀏覽器跨域請求後,根據自身配置返回對應檔案頭。若未配置過任何允許跨域,則檔案頭裡不包含Access-Control-Allow-origin欄位,若配置過域名,則返回Access-Control-Allow-origin對應配置規則裡的域名的方式

  3. 瀏覽器根據接受到的http檔案頭裡的Access-Control-Allow-origin欄位做匹配,若無該欄位,說明不允許跨域;若有該欄位,則對欄位內容和當前域名做比對,如果同源,則說明可以跨域,瀏覽器傳送該請求;若不同源,則說明該域名不可跨域,不傳送請求

(但是不能僅僅根據伺服器返回的檔案頭裡是否包含Access-Control-Allow-origin來判斷其是否允許跨域,因為伺服器端配置多域名跨域的時候,也會出現不能跨域的域名返回包裡沒有Access-Control-Allow-origin欄位的情況。下文配置說明裡會講。)

配置伺服器實現跨域傳輸

前面講到了同源策略的基本判定,以及瀏覽器實現跨域判斷的方式,那麼,如何在伺服器端做配置來允許跨域傳輸呢?下文將以Nginx為例,講一下三種情況下的配置。

配置項解析

CORS常用的配置項有以下幾個:

  • Access-Control-Allow-Origin(必含) – 允許的域名,只能填萬用字元或者單域名

  • Access-Control-Allow-Methods(必含) – 這允許跨域請求的http方法(常見有POSTGETOPTIONS

  • Access-Control-Allow-Headers(當預請求中包含Access-Control-Request-Headers時必須包含) – 這是對預請求當中Access-Control-Request-Headers的回覆,和上面一樣是以逗號分隔的列表,可以返回所有支援的頭部。

  • Access-Control-Allow-Credentials(可選) – 該項標誌著請求當中是否包含cookies資訊,只有一個可選值:true(必為小寫)。如果不包含cookies,請略去該項,而不是填寫false。這一項與XmlHttpRequest2物件當中的withCredentials屬性應保持一致,即withCredentials為true時該項也為true;withCredentials為false時,省略該項不寫。反之則導致請求失敗。

  • Access-Control-Max-Age(可選) – 以秒為單位的快取時間。預請求的的傳送並非免費午餐,允許時應當儘可能快取。

具體配置舉例

全域名或者單域名允許跨域

這個最省心
開啟Nginx的配置檔案(預設為nginx.conf)。找到對應域名設定的local配置部分。
新增以下內容:

add_header 'Access-Control-Allow-origin' 'http://www.example.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';

新增的域名必須帶http://協議頭(否則伺服器無法區分是http還是https),如果接受所有域名的跨域請求,則可以用*(安全性有問題,不推薦)

新增多域名跨域配置

如果允許跨域的域名有多個但出於安全問題又不想配置全域名通配的時候,就可以用到nginx裡的if判斷了。
新增如下內容:

if ($http_origin = 'http://segmentfault.com' ) {  
 add_header 'Access-Control-Allow-Origin' "$http_origin";
 }
if ($http_origin = 'http://localhost:4000' ) {  
 add_header 'Access-Control-Allow-Origin' "$http_origin";
 }
 add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
  • 如果對正則比較熟悉的,可以直接用正則來匹配條件判斷,不需要用if這麼麻煩的方式。

  • 'Access-Control-Allow-Methods' 允許多引數,'Access-Control-Allow-origin'不允許多引數,所以只能是條件語句判斷要不要加這個。這也是我前面提到的為什麼即使HTTP檔案頭返回值裡沒有'Access-Control-Allow-origin',也不能說明它就是不允許跨域的。

  • nginx配置檔案的http配置部分不能用if條件語句,所以多域名的時候必須加在local部分內。另外加在local內的只對對應的伺服器域名做跨域請求的配置,加在http裡會讓跑在該nginx下的所有網站都統一採取這種配置。

  • Access-Control-Allow-Origin也可以改成全小寫的形式,不影響結果.(access-control-allow-origin也可以)