HTTP 訪問控制(CORS)

  跨域請求:請求不屬於自己域(domain)下資源。例如,一個來自http://domain-a.com域下的HTML page請求http://domain-b.com域的圖片資源。當今,網站中許多頁面從別的域下加載像CSS stylesheets, images and scripts資源。
  出於安全考慮,瀏覽器會限制跨域腳本執行請求。例如,XMLHttpRequest遵循同源策略(所謂同源是指,域名,協議,端口相同;舉個簡單例子,當瀏覽器的百度tab頁執行一個腳本的時候會檢查這個腳本是屬於哪個頁面的,即檢查是否同源,只有和百度同源的腳本纔會被執行)。因此,一個網站使用XMLHttpRequest只能請求它自己域下的資源。爲了提升web應用,開發者請求瀏覽器供應商允許XMLHttpRequest做出跨域請求。
  W3C Web Applications Working Group推出了一種新的機制叫Cross-Origin Resource Sharing(跨域資源共享,簡寫爲CORS)。CORS對服務器跨域做了訪問控制,使跨域數據能夠安全傳輸。現代瀏覽器在API容器當中使用CORS,比如,XMLHttpRequest,來降低跨域請求的風險。
  現代瀏覽器會處理客戶端跨域資源共享的請求內容,包括請求頭以及策略的執行。添加了新的標準意味着服務器就要處理新的請求頭以及返回對應新的響應頭。
跨域共享標準可以使以下進行跨站點請求:
1. 向上面講述的一樣,在跨域行爲中調用XMLHttpRequest API
2. Web字體,以至於服務器可以部署那些只能被跨域站點加載和那些被允許使用的網站字體
3. WebGL textures
4. Images/video frames drawn to a canvas using drawImage
5. css樣式
6. script腳本


概述

  跨域資源共享標準通過增加新的HTTP請求頭,允許服務器描述一系列被允許使用瀏覽器讀取信息的請求。除此之外,對於那些可能給用戶數據造成負面影響HTTP請求方式,瀏覽器會對請求進行”預檢“,通過OPTIONS請求方式從服務器上拉取被支持的請求方式,那時,得到了服務器的允許,再發送真正的HTTP請求方式。服務器也可以通知客戶端是否請求時要帶上“證書”。
接下來的部分討論一些使用場景,以及對HTTP請求頭使用分解。


訪問控制場景例子

  在這裏,我們呈現三個場景以闡述跨域資源共享是怎麼工作的。所有的這些例子都是使用XMLHttpRequest對象,它能夠被所有支持的瀏覽器進行跨站點調用。
  這些javascript代碼能夠執行在支持跨站點的XMLHttpRequest瀏覽器上。現在從服務器的角度來討論跨域資源共享。


簡單請求

一個簡單的跨站點請求需要滿足以下條件:

只允許如下請求方式:

  • GET
  • HEAD
  • POST

除了被用戶代理自動設置的請求頭,下面請求頭可以允許手動設置:

  • Accept
  • Accept-Language
  • Content-Language(表示當前頁面的語言)
  • Content-Type
 <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> 
 <meta http-equiv="Content-Language" content="zh-CN">

charset和Content-Language的含義就是,當前頁面的語言是中文的,請瀏覽器使用UTF-8編碼字符集進行解碼顯示
1. Content-Type被允許使用的值爲:
* application/x-www-form-urlencoded
* multipart/form-data
* text/plain
例如,假設在域http://foo.example下的內容希望調用在域http://bar.other下的內容。那麼寫在域foo.example中的javascript代碼如下:

var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/public-data/'; 
function callOtherDomain() {
  if(invocation) {    
    invocation.open('GET', url, true);
    invocation.onreadystatechange = handler;
    invocation.send(); 
  }
}

讓我們來看一下發往服務器端的請求內容,以及服務器端的響應內容:

GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/access-control/simpleXSInvocation.html
Origin: http://foo.example


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2.0.61 
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml

[XML Data]

空白行下面的內容,是服務端響應內容。在響應內容中,服務器返回了Access-Control-Allow-Origin頭部。在這個例子中,服務端返回Access-Control-Allow-Origin: *表明任何跨域訪問站點都可以訪問http://bar.other域下的資源。如果資源的擁有域http://bar.other只希望http://foo.example域訪問它的資源那麼可以修改成如下:

Access-Control-Allow-Origin: http://foo.example

預檢請求

  不像簡單的請求,預檢請求第一次以OPTIONS方式請求其他域的資源,這樣是爲了確保真正的請求安全發送。由於跨站點請求可能對用戶數據產生影響,因此它需要進行預檢。下面列出了一些需要預檢請求特點:
- 請求的方式不是GET、HEAD、POST其中之一。還有,如果POST發送數據類型(Content-Type)不是application/x-www-form-urlencoded、multipart/form-data、text/plain,比如POST發送的數據是XML格式,application/xml、text/xml,那時該請求需要預檢。
- 設置自定義的請求頭部(比如X-PINGOTHER)
下面是例子:

var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/post-here/';
var body = '<?xml version="1.0"?><person><name>Arun</name></person>';  
function callOtherDomain(){
  if(invocation)
    {
      invocation.open('POST', url, true);
      invocation.setRequestHeader('X-PINGOTHER', 'pingpong');
      invocation.setRequestHeader('Content-Type', 'application/xml');
      invocation.onreadystatechange = handler;
      invocation.send(body); 
    }
}

例子中,第3行創建了XML內容並且以POST方式進行請求。還有,在第8行自定義了一個請求頭(X-PINGOTHER:pingpong)。這樣的請求頭不是HTTP/1.1一部分,但是普遍的應用在web應用中。由於這個請求使用了POST並且Content-Type值不是上面講到的三個之一,並且自定義了一個頭部,因此這個請求就要進行預檢。
讓我們看看客戶端與服務端完整的請求與響應內容:

OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

POST /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: http://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: http://foo.example
Pragma: no-cache
Cache-Control: no-cache

<?xml version="1.0"?><person><name>Arun</name></person>


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain

[Some GZIP'd payload]

1~12行就是OPTIONS方式進行預檢請求。OPTIONS方式是HTTP/1.1的請求方式,是用來進一步確定來自服務端的信息,並且它是一個冪等(在編程中,一個冪等操作的特點是其任意多次執行所產生的影響均與一次執行的影響相同;冪等函數,或冪等方法,是指可以使用相同參數重複執行,並能獲得相同結果的函數,GET,HEAD, PUT, DELETE方式都是冪等的)的方法,這意味着它不能改動資源,有兩個請求頭隨着OPTIONS請求發送(第10行和第11行):

Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

Access-Control-Request-Method就是告知服務器,當我發起真正請求時,請求方式是POST。The Access-Control-Request-Headers通知服務器,當我發起真正請求時,將發送自定義值X-PINGOHER和Content-Type的請求頭。現在服務器就可以有機會決定,在這樣的環境下它是否希望接受請求。
14~26行服務器端送回的響應表明請求方式POST和請求頭X-PINGOTHER可接受。特別看一下17~20行:

Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400

Access-Control-Allow-Methods表明POST、GET、OPTIONS是可以訪問資源請求方式;Access-Control-Allow-Headers表明請求時允許使用X-PINGOTHER、Content-Type頭部。它們的值之間以逗號隔開。
Access-Control-Max-Age表明經過多久纔會進行下一次預檢。


認證請求

  最令人感興趣的是XMLHttpRequest和Access Control能夠發送認證請求,認證請求瀏覽器默認是關閉的。具體認證請求看下面例子:

var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/credentialed-content/';

function callOtherDomain(){
  if(invocation) {
    invocation.open('GET', url, true);
    invocation.withCredentials = true;
    invocation.onreadystatechange = handler;
    invocation.send(); 
  }
}

啓用認證只需要也就是withCredentials=true。這樣瀏覽器將拒絕任何不帶 Access-Control-Allow-Credentials: true頭部的響應,具體看下面內容:

GET /resources/access-control-with-credentials/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/credential.html
Origin: http://foo.example
Cookie: pageAccess=2


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:34:52 GMT
Server: Apache/2.0.61 (Unix) PHP/4.4.7 mod_ssl/2.0.61 OpenSSL/0.9.7e mod_fastcgi/2.4.2 DAV/2 SVN/1.4.2
X-Powered-By: PHP/5.2.6
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 106
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain


[text/plain payload]

儘管11行向http://bar.other發送了Cookie,如果bar.other不響應 Access-Control-Allow-Credentials: true頭部,這個服務端響應將被客戶端拒絕。重要提示:當響應認證請求時,服務端必須指定域,不能使用通配符,例如:Access-Control-Allow-Origin: *。


從這篇文章當中瞭解到跨域訪問很多知識,自己也明朗了許多,寫的非常好,於是就翻譯出來,如有翻譯不當的地方還望見諒!

                                                            -_-沒有天才,只有勤奮的天才

被譯文地址:https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章