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

前端對Cross-Origin Resource Sharing 問題(CORS,中文又稱'跨域')應該很熟悉了。衆所周知出於安全的考慮,瀏覽器有個同源策略,對於不一樣源的站點之間的相互請求會作限制(跨域限制是瀏覽器行爲,不是服務器行爲。)。不過下午想到了一個略無趣的問題:瀏覽器和服務器究竟是如何斷定有沒有跨域呢?本文主要分兩個部分,一是對這個問題的總結,二是nginx下如何配置服務器容許跨域。
<!-- more -->javascript

同源策略

同源指的是域名(或IP),協議,端口都相同,不一樣源的客戶端腳本(javascript、ActionScript)在沒明確受權的狀況下,不能讀寫對方的資源。html

同源的斷定:
http://www.example.com/dir/page.html爲例,如下表格指出了不一樣形式的連接是否與其同源:(緣由裏未申明不一樣的屬性即說明其與例子裏的原連接對應的屬性相同)前端

連接 結果 緣由
http:// www.example.com /dir/page2.html 同協議同域名同端口
http:// www.example.com /dir2/other.html 同協議同域名同端口
http://user:pwd@ 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)。當域名不一樣源的時候,因爲跨域實現的存在,瀏覽器不能直接根據域名來斷定跨域限制。那麼瀏覽器具體又是如何實現斷定的呢?看下面的例子。html5

環境說明

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

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

  3. 檢測方法:在各個域名下利用chrome瀏覽器的console界面模擬發送ajax請求,代碼以下:ajax

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

實驗過程

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

    服務器迴應的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域名下,請求成功json

    服務器迴應的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文件頭內容會視服務器是否有配置跨域請求而發生變化segmentfault

服務器容許跨域請求

(僅容許來自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也能夠)

PS:
這篇文章寫的可能有點繞,另外因爲運維方面和WEB安全方面不是特別熟悉,因此後面配置那裏未必是最優解,懇請看到的各位指點。

參考文獻

  1. Same-origin policy

  2. HTTP access control (CORS)

  3. Using CORS

  4. 利用CORS實現跨域請求 (是參考文獻2的中譯版,配置參數解析那裏有所參考)

  5. enable cross-origin resource sharing

相關文章
相關標籤/搜索