同源策略與CORS

同源策略

  • 它是瀏覽器最核心也是最基本的安全功能,瀏覽器的同源策略,限制了來自不同的 “document” 或腳本對(duì)當(dāng)前的 “document” 的讀取或設(shè)置某些屬性
  • 如果沒有同源策略,可能 a.com 的一段 Javascript 腳本在 b.com 未曾加載此腳本時(shí),也可以隨意涂改 b.com 的頁面,為了不發(fā)生頁面行為混亂,瀏覽器提出 ”O(jiān)rigin” 這一概念,來自不同 Origin 的對(duì)象互不干擾。
  • 影響 Origin(源) 的因素有:host(域名或IP)、子域名、端口、協(xié)議:
URL OutCome Reason
http://store.company.com/dir2/other.html Success
http://store.company.com/dir/inner/anthoer.html Success
https://store.company.com/secure.html Failure Different protocol
http://store.company.com:81/dir/etc.html Failure Different port
http://news.company.com/dir/other.html Failure Different host
  • 在瀏覽器中 <script><img><iframe><link> 等標(biāo)簽都可以跨域加載資源,而不受到同源策略的限制。這些帶 “src” 屬性的標(biāo)簽每次加載時(shí),實(shí)際上是由瀏覽器發(fā)起了一次GET請(qǐng)求。不同于 XMLHttpRequest 的是,通過 src 屬性加載的資源,瀏覽器限制了 JavaScript 的權(quán)限,使其不能讀、寫返回內(nèi)容。
  • XMLHttpRequest 可以訪問同源資源,一開始不能跨域訪問,但隨著互聯(lián)網(wǎng)發(fā)展,W3C制定了 XMLHttpRequest 跨域訪問的標(biāo)準(zhǔn)(CORS),它需要通過目標(biāo)與返回的HTTP頭來授權(quán)是否可以跨域訪問,因?yàn)?strong>HTTP頭對(duì)于 JavaScript 來說一般無法控制
  • 但是瀏覽器的同源策略并非堅(jiān)不可摧,如 IE8 的 Css 跨域漏洞:
www.a.com/test.html:
    <body>
    {}body{font-family:
    aaaaaaaaaaaaaaa
    bbbbbbbbbbbbbbbbb
    </body>
www.b.com/test2.html:
    <style>
    @import url("http://www.a.com/test.html")
    </style>
    <script>
        setTimeout(function(){
            var t = document.body.currentStyle.fontFamily;
            alert(t);
        },2000);
    </script>
  • 上述代碼中 www.b.com/test2.html 中通過 @import 加載了 www.a.com/test.html 為 Css 渲染進(jìn)入當(dāng)前頁面 DOM,同時(shí)通過 document.body.currentStyle.fontFamily 訪問此內(nèi)容,問題在 IE 的 CSS Parse 過程中,IE 將 fontFamily 后面的內(nèi)容當(dāng)做 value,而通過 <script> 等標(biāo)簽僅能加載資源,不能讀寫資源內(nèi)容,而這個(gè)漏洞能夠跨域讀取頁面內(nèi)容,繞過了同源策略

CORS

  • CORS 是 W3C 制定的“跨域資源共享”的標(biāo)準(zhǔn),它允許瀏覽器向跨源服務(wù)器發(fā)出 XMLHttpRequest 請(qǐng)求,克服了 AJAX 只能同源的使用限制
  • CORS 需要瀏覽器和服務(wù)器同時(shí)支持,目前所有瀏覽器都支持該功能,IE 瀏覽器不能低于 IE10,整個(gè)通信過程是瀏覽器自動(dòng)完成的,不需要用戶參與,CORS 與 AJAX 代碼上沒有差別,瀏覽器一旦發(fā)現(xiàn) AJAX 請(qǐng)求跨源,就會(huì)自動(dòng)添加一些附加的頭信息
  • 因此實(shí)現(xiàn) CORS 的通信關(guān)鍵是服務(wù)器,只要服務(wù)器實(shí)現(xiàn)了 CORS 接口,就可以跨源通信

兩種請(qǐng)求

  • 瀏覽器將 CORS 請(qǐng)求分為兩類:簡單請(qǐng)求(Simple Request)和非簡單請(qǐng)求(not-so-simple reuqest)
  • 只要滿足以下兩種條件就屬于簡單請(qǐng)求,否則屬于非簡單請(qǐng)求
請(qǐng)求方法是以下三種方法之一:
    HEAD、GET、POST
HTTP的頭信息不超出以下幾種字段:
    Accept、Accept-Language、Content-Language、Last-Event-ID
    Content-Type:只限于三個(gè)值application/x-www-form-urlencoded、multipart/form-data、text/plain

簡單請(qǐng)求

  • 對(duì)于簡單請(qǐng)求,瀏覽器直接發(fā)出 CORS 請(qǐng)求,就是在頭信息中添加一個(gè) Origin 字段
  • 下面是瀏覽器在一個(gè)請(qǐng)求中添加 Origin 字段的例子
GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
  • 上面頭信息中,Origin 字段用來說明本次請(qǐng)求來自哪個(gè)源(協(xié)議+域名+端口),服務(wù)器根據(jù)這個(gè)值,決定是否同意本次請(qǐng)求
  • 如果 Origin 指定的源不在許可范圍內(nèi),服務(wù)器會(huì)返回一個(gè)正常的 HTTP 協(xié)議,但頭信息沒有包含 Access-Control-Allow-Origin 字段,從而拋出一個(gè)錯(cuò)誤,被 XMLHttpRequest的onerror 回調(diào)函數(shù)捕獲。注意,這種錯(cuò)誤無法通過狀態(tài)碼識(shí)別,因?yàn)?HTTP 回應(yīng)的狀態(tài)碼有可能是200
  • 如果 Origin 指定的域名在許可范圍內(nèi),服務(wù)器的響應(yīng)會(huì)多出幾個(gè)頭信息
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
  • Access-Control-Allow-Origin:該字段是必須的。它的值要么是請(qǐng)求時(shí) Origin 字段的值,要么是一個(gè)*,表示接受任意域名的請(qǐng)求
  • Access-Control-Allow-Credentials:該字段可選,它是一個(gè)布爾值,表示是否允許發(fā)送 Cookie
  • Access-Control-Expose-Headers:該字段可選,CORS 請(qǐng)求時(shí),XMLHttpRequest 對(duì)象的 getResponseHeader() 方法只能拿到 6 個(gè)基本字段:ache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果想拿到其它字段就必須在 Access-Control-Expose-Headers 里面指定,上面例子指定了 getResponseHeader('FooBar') 可以返回 FooBar 字段的值

withCredentials 屬性

  • CORS 請(qǐng)求默認(rèn)不發(fā)送 Cookie 和 HTTP 認(rèn)證信息,如果要把 Cookie 發(fā)送到服務(wù)器,一方面要服務(wù)器同意
Access-Control-Allow-Credentials = true
  • 另一方面開發(fā)者必須在 AJAX 請(qǐng)求中打開 withCredentials 屬性
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
  • 有些瀏覽器會(huì)默認(rèn)一起發(fā)送 Cookie,這時(shí)可以顯式關(guān)閉 withCredentials
xhr.withCredentials = false;
  • 如果要發(fā)送 Cookie,Access-Control-Allow-Origin 就不能為星號(hào),必須指定明確的、與請(qǐng)求網(wǎng)頁一致的域名,并且 Cookie 依然遵循同源策略

非簡單請(qǐng)求

  • 非簡單請(qǐng)求是那種對(duì)服務(wù)器有特殊需求的請(qǐng)求,比如 PUT 或 DELETE 方法,或者 Content-Type 字段的類型是 application/json
  • 非簡單請(qǐng)求的 CORS 請(qǐng)求會(huì)在正式通信之前增加一次 HTTP 請(qǐng)求查詢,稱為“預(yù)檢”請(qǐng)求
  • 瀏覽器會(huì)先詢問服務(wù)器,當(dāng)前網(wǎng)頁是否在服務(wù)器的許可名單中,以及可以使用那些 HTTP 動(dòng)詞和頭字段信息,只有得到肯定大幅,瀏覽器才會(huì)發(fā)送 XMLHttpRequest 請(qǐng)求,否則報(bào)錯(cuò)
  • 下面是一段瀏覽器的 JS 腳本
var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();
  • 上面代碼 HTTP 請(qǐng)求方法是 PUT,并且發(fā)送一個(gè)自定義的頭信息 X-Custom-Header
  • 瀏覽器發(fā)現(xiàn),這是一個(gè)非簡單請(qǐng)求,就自動(dòng)發(fā)出一個(gè)“預(yù)檢”請(qǐng)求,要求服務(wù)器確認(rèn),下面是一個(gè)“預(yù)檢”請(qǐng)求的例子
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
  • “預(yù)檢”請(qǐng)求的方法是 OPTIONS,表示這個(gè)請(qǐng)求是用來詢問的,頭信息里關(guān)鍵字段是 Origin,表示來自哪個(gè)源
  • 除了 Origin 字段,還包括兩個(gè)特殊字段
    • Access-Control-Request-Method:必須,用來列出 CORS 請(qǐng)求用的 HTTP 方法,上例是 PUT
    • Access-Control-Request-Headers:該字段是一個(gè)逗號(hào)分離的字符串,指定額外發(fā)送的頭字段信息,上例是 X-Custom-Header

預(yù)檢請(qǐng)求的回應(yīng)

  • 服務(wù)器收到“預(yù)檢”請(qǐng)求后檢查 Origin、Access-Control-Request-Method、Access-Control-Request-Headers 字段后確認(rèn)允許跨源請(qǐng)求,就可以作出回應(yīng)
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://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
  • 上面例子中 Access-Control-Allow-Origin 字段表示 http://api.bob.com 可以請(qǐng)求數(shù)據(jù),該字段也可以為星號(hào),表示同意任意跨源請(qǐng)求
Access-Control-Allow-Origin: *
  • 如果瀏覽器否定了“預(yù)檢”請(qǐng)求會(huì)返回一個(gè)正常的 HTTP 回應(yīng),但不包含 CORS 相關(guān)頭信息,因此會(huì)觸發(fā)一個(gè)錯(cuò)誤,被 XMLHttpRequest 對(duì)象的 onerror 回調(diào)函數(shù)捕獲
  • 服務(wù)器回應(yīng)的其它 CORS 相關(guān)字段如下:
Access-Control-Allow-Methods: GET, POST, PUT  #表示支持的跨域請(qǐng)求方法
Access-Control-Allow-Headers: X-Custom-Header  #表示服務(wù)器支持的所有頭信息字段
Access-Control-Allow-Credentials: true  #該字段與簡單請(qǐng)求時(shí)相同
Access-Control-Max-Age: 1728000  #指定本次預(yù)檢請(qǐng)求的有效期,單位為秒,即在緩存該條回應(yīng)期間,不用在發(fā)出另一條預(yù)檢請(qǐng)求
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容