同源策略
- 它是瀏覽器最核心也是最基本的安全功能,瀏覽器的同源策略,限制了來自不同的 “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é)議:
- 在瀏覽器中 <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)求