跨域技術與安全

CORS

一、CORS內(nèi)部機制與實現(xiàn)

CORS(Cross-Origin Resource Sharing,跨域資源共享),它允許瀏覽器向服務器發(fā)出XMLHttpRequest請求,從而克服AJAX只能同源使用的限制。

PS:同源:協(xié)議相同,域名相同,端口相同。http://www.example.com/dir/page.html,協(xié)議是http://,域名是:/www.example.com,端口是80(默認省略)。非同源,Cookie、LocalStorage 和 IndexDB無法讀取,DOM 無法獲得,AJAX請求不能發(fā)送

http://www.example.com/dir2/other.html:同源
http://example.com/dir/other.html:不同源(域名不同) http://v2.www.example.com/dir/other.html:不同源(域名不同)
http://www.example.com:81/dir/other.html:不同源(端口不同)

CORS需要瀏覽器和服務器同時支持。目前使用XMLHttpRequest實現(xiàn)的方式(僅支持異步),所有瀏覽器都支持,但IE10+,低版本的IE有其他實現(xiàn)方式。
CORS的背后思想:使用自定義的HTTP頭部,讓瀏覽器與服務器進行溝通,從而決定請求或響應是該成功還是應該失敗。在發(fā)送請求時,瀏覽器自行附加一個Origin頭部,包含發(fā)出請求頁面的原信息(協(xié)議、域名和端口),然后服務器根據(jù)這個頭部信息來決定是否給予響應。

客戶端發(fā)出Origin:http://www.abc.com,如果服務器認為這個請求可以接受,就返回Access-Control-Allow-Origin:http://www.abc.com,如果沒有該返回頭部,或者頭部源信息不匹配,瀏覽器就會駁回請求。

整個CORS通信過程,都是瀏覽器自動完成,不需要用戶參與。對于開發(fā)者來說,CORS通信與同源的AJAX通信沒有差別,代碼完全一樣。瀏覽器一旦發(fā)現(xiàn)AJAX請求跨源(請求的url跨域路徑是絕對路徑),就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感覺。

二、兩種請求

CORS分為簡單請求和非簡單請求。
簡單請求同時滿足以下兩大條件:

1.請求方式是以下三種方法之一:
HEAD ,GET, POST
2.HTTP的頭部信息不超出以下幾種字段:
Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type:只限于三個值,application/x-www-from-urlencoded(表單序列化)、multipart/form-data(上傳文件)、text/plain(純文本的形式,瀏覽器在獲取到這種文件時并不會對其進行處理)

凡是不同時滿足上面兩個條件,就是非簡單請求。瀏覽器的處理方式不一樣。

三、簡單請求

1.基礎信息
簡單請求,瀏覽器直接發(fā)出CORS請求。在頭信息增加Origin字段
瀏覽器簡單AJAX跨域請求:

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...

服務器根據(jù)Origin的值來決定是否同意請求。如果Origin指定的源,不在許可范圍內(nèi),服務器會返回一個正常的HTTP響應。瀏覽器發(fā)現(xiàn),這個回應的頭信息沒有包含Access-Control-Allow-Origin字段,就知道出錯了,從而拋出一個錯誤,被XMLHttpRequestonerror回調(diào)函數(shù)捕獲。注意,這種錯誤無法通過狀態(tài)碼識別,因為HTTP回應的狀態(tài)碼有可能是200。如果服務器接受 Origin,頭部信息會多出幾個信息字段。

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

上面的頭信息之中,有三個與CORS請求相關的字段,都以Access-Control-開頭。
(1)Access-Control-Allow-Origin
該字段是必須的。它的值要么是請求時Origin字段的值,要么是一個*,表示接受任意域名的請求。
(2)Access-Control-Allow-Credentials
該字段可選。它的值是一個布爾值,表示是否允許發(fā)送Cookie。默認情況下,Cookie不包括在CORS請求之中。設為true,即表示服務器明確許可,Cookie可以包含在請求中,一起發(fā)給服務器。這個值也只能設為true,如果服務器不要瀏覽器發(fā)送Cookie,刪除該字段即可。
(3)Access-Control-Expose-Headers
該字段可選。CORS請求時,XMLHttpRequest對象的getResponseHeader()方法只能拿到6個基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必須在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值。
2.withCredentials 屬性
CORS請求默認不發(fā)送Cookie和HTTP認證信息。如果要把Cookie發(fā)到服務器,一方面要服務器同意,指定Access-Control-Allow-Credentials字段。

Access-Control-Allow-Credentials:true

另一方面,開發(fā)者必須在AJAX請求中打開withCredentials屬性。

var xhr = new XMLHttpRequest();   xhr.withCredentials = true;

否則,即使服務器同意發(fā)送Cookie,瀏覽器也不會發(fā)送?;蛘?,服務器要求設置Cookie,瀏覽器也不會處理。
但是,如果省略withCredentials設置,有的瀏覽器還是會一起發(fā)送Cookie。這時,可以顯式關閉withCredentials。

xhr.withCredentials = false;

需要注意的是,如果要發(fā)送Cookie,Access-Control-Allow-Origin就不能設為星號,必須指定明確的、與請求網(wǎng)頁一致的域名。同時,Cookie依然遵循同源政策,只有用服務器域名設置的Cookie才會上傳,其他域名的Cookie并不會上傳,且(跨源)原網(wǎng)頁代碼中的document.cookie也無法讀取服務器域名下的Cookie。

四、非簡單請求

1.預檢請求
非簡單請求時對服務器有特殊要求的請求,例如請求方法是,PUT(讓服務器創(chuàng)建一個文件,文件名是請求的url,文件內(nèi)容是請求報文的主體)|DELETE(刪除請求url指定的資源);或者Content-Type字段是application/json
非簡單請求,正式通信之前,增加一次HTTP查詢請求,叫做“預檢請求”
瀏覽器先詢問服務器,當前網(wǎng)頁所在的域名是否在服務器的許可名單之中,以及可以使用那些HTTP動詞(請求方法)和頭部信息字段。只有得到肯定的答復,瀏覽完才會正式發(fā)出XMLHttpRequest請求,否則就報錯。
JS腳本,PUT請求,且發(fā)送一個自定義頭信息X-Custom-Header

> var url = 'http://api.alice.com/cors';
> var xhr = new XMLHttpRequest();
> xhr.open('PUT', url, true);
> xhr.setRequestHeader('X-Custom-Header', 'value');
> xhr.send();

瀏覽器發(fā)現(xiàn),這是一個非簡單請求,就自動發(fā)出一個“預檢請求”,要求服務器確認允許這樣的請求。下面是一個預檢請求的頭信息。

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...

“預檢請求”使用的請求方法是OPTIONS,表示這個請求是用來詢問的。頭信息里面,關鍵字段是Origin,表示請求來自哪個源。
除了Origin字段,"預檢"請求的頭信息包括兩個特殊字段。
(1)Access-Control-Request-Method
該字段是必須的,用來列出瀏覽器的CORS請求會用到哪些HTTP方法,上例是PUT。
(2)Access-Control-Request-Headers
該字段是一個逗號分隔的字符串,指定瀏覽器CORS請求會額外發(fā)送的頭信息字段,上例是X-Custom-Header
2.預檢請求回應
服務器收到"預檢"請求以后,檢查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,確認允許跨源請求,就可以做出回應。

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

上面的HTTP回應中,關鍵的是Access-Control-Allow-Origin字段,表示http://api.bob.com可以請求數(shù)據(jù)。該字段也可以設為星號,表示同意任意跨源請求。
如果瀏覽器否定了預檢請求,返回一個正常的HTTP回應,但是么有任何CROS相關的頭信息字段。這時,瀏覽器就會認定,服務器不同意預檢請求,因此觸發(fā)一個錯誤,被XMLHttpRequest對象的onerror回調(diào)函數(shù)捕獲。
服務器回應的其他CORS相關字段如下。

Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true

(1)Access-Control-Allow-Methods
該字段必需,它的值是逗號分隔的一個字符串,表明服務器支持的所有跨域請求的方法。注意,返回的是所有支持的方法,而不單是瀏覽器請求的那個方法。這是為了避免多次"預檢"請求。
(2)Access-Control-Allow-Headers
如果瀏覽器請求包括Access-Control-Request-Headers字段,則Access-Control-Allow-Headers字段是必需的。它也是一個逗號分隔的字符串,表明服務器支持的所有頭信息字段,不限于瀏覽器在"預檢"中請求的字段。
(3)Access-Control-Allow-Credentials
該字段與簡單請求時的含義相同。
(4)Access-Control-Max-Age
該字段可選,用來指定本次預檢請求的有效期,單位為秒。上面結(jié)果中,有效期是20天(1728000秒),即允許緩存該條回應1728000秒(即20天),在此期間,不用發(fā)出另一條預檢請求。
Access-Control-Max-Age: 1728000

  1. 瀏覽器的正常請求和回應
    一旦服務器通過了"預檢"請求,以后每次瀏覽器正常的CORS請求,就都跟簡單請求一樣,會有一個Origin頭信息字段。服務器的回應,也都會有一個Access-Control-Allow-Origin頭信息字段。
    下面是"預檢"請求之后,瀏覽器的正常CORS請求。
> PUT /cors HTTP/1.1
> Origin: http://api.bob.com
> Host: api.alice.com
> X-Custom-Header: value
> Accept-Language: en-US
> Connection: keep-alive
> User-Agent: Mozilla/5.0...

上面頭信息的Origin字段是瀏覽器自動添加的。
下面是服務器正常的回應。

> Access-Control-Allow-Origin: http://api.bob.com
> Content-Type: text/html; charset=utf-8

上面頭信息中,Access-Control-Allow-Origin字段是每次回應都必定包含的。

五、跨瀏覽器的CORS實現(xiàn)

所有瀏覽器都支持簡單請求(非預檢請求和不帶憑據(jù)的請求),因此有必要做跨瀏覽器方案。檢測XHR是否支持CORS的最簡單方式就是就是檢查xhr中是否存在withCredentials屬性。再結(jié)合XDomainRequest對象(IE10以下)是否存在,就可以兼顧所有瀏覽器了。

function creaeCORSRequest(method,url){
  var xhr=new XMLHttpRequest();
  if("withCredentials" in xhr){
      xhr.open(method,url,true);
  }else if(typeof XDomainRequest!="undefined"){
     xhr=new XDomainRequest();
      xhr.open(method,url);//沒有第三個參數(shù)     
  }else{
    xhr=null;
  }
return xhr;
}

var request=createCORSRequest("get","http://www.abc.com");
if(request){
  request.onload=function(){
      console.log(request.responseText);//處理響應信息
  };
request.onerror=function(){
      //處理錯誤
  }
request.send();
}

onerror和onload分別代替onreadystatechange的檢測錯誤和檢測成功

JSONP

1、什么是JSONP
JSONP是JSON with padding的縮寫,通過創(chuàng)建script元素來使用,讓其src屬性為一個跨域的URL,在URL后面添加響應后的回調(diào)函數(shù),響應的數(shù)據(jù)以參數(shù)的形式傳入回調(diào)函數(shù)。
2、如何使用jsonp跨域

//res響應數(shù)據(jù)
function handleResponse(res){
    console.log(res)
}
var script=document.createElement('script')
//handleResponse回調(diào)函數(shù)
script.src="http://freegeoip.net/json/?callback=handleResponse";
document.body.insertBefore(script,document.body.firstChild)

3、JSONP優(yōu)缺點
優(yōu)點:能直接訪問響應文本,支持在瀏覽器與服務器之間雙向通信。
缺點:其他域可能有惡意腳本,jsonp的請求是否失敗不容易無額定
4、cors與jsonp比較
CORS與JSONP的使用目的相同,但是比JSONP更強大。
JSONP只支持GET請求,CORS支持所有類型的HTTP請求。JSONP的優(yōu)勢在于支持老式瀏覽器,以及可以向不支持CORS的網(wǎng)站請求數(shù)據(jù)。

圖像Ping

1、什么是圖像Ping
圖像Ping是與服務器進行簡單、單向的跨域通信的一種方式,請求的數(shù)據(jù)通過查詢字符串的形式發(fā)送,響應可以是任意內(nèi)容,但通常是像素圖或者204(204 No Content成功狀態(tài)響應碼表示目前請求成功,但客戶端不需要更新其現(xiàn)有頁面。204 響應默認是可以被緩存的。在響應中需要包含頭信息 ETag)。通過圖像Ping,瀏覽器得不到任何具體的數(shù)據(jù),但通過偵聽load和error事件,就能知道響應是什么時候接收到的。
2、實現(xiàn)樣例

var img=new Image();
img.onload=img.onerror=function(){
  console.log('done')
}
img.src="http://www.example.com/test?name=Nicholas";

修改document.domain跨子域通信

1、適用情況
當某個頁面中包含其他子域的框架或者內(nèi)嵌框架時,可以通過將每個頁面的document.domain設置為相同的值來通信,設置的域名應該是他們的父域。瀏覽器對domain有一個限制,如果一開始document.domain是松散的,那么就不能再緊繃。例如

//假設頁面來自于p2p.wrox.com 域
document.domain = "wrox.com"; //松散的(成功)
document.domain = "p2p.wrox.com"; //緊繃的(出錯!)

使用栗子

//html,在http://example.com/a.html中嵌入iframe
<iframe src="http://example.com/b.htm" id=''iframe"  onload = "test()"></iframe>
//http://example.com/a.html中js
 document.domain =‘example.com’
 function test(){
        alert(document.getElementById('iframe').contentWindow);//contentWindow 可取得子窗口的 window 對象
    }
//http://example.com/b.html中js
 document.domain =‘example.com’

跨文檔消息傳遞

1、是什么
跨文檔消息傳送(cross-document messaging),有時候簡稱為XDM,指來自不同域的頁面間傳遞消息。一般向包含在當前頁面中的iframe元素或者由當前頁面彈出的窗口傳遞。例如:比如www.baidu.com域的A頁面通過iframe嵌入了一個google.com域的B頁面。XDM核心方法是postMessage(message,tourl)方法,兩個參數(shù),第一個是消息,第二個是把消息傳給誰。接收到XDM消息后,觸發(fā)windowonmessage事件。
2、如何用
A頁面通過postMessage方法發(fā)送消息:

//支持XDM的瀏覽器也支持iframe的contentWindow屬性
window.onload = function() {  
    var ifr = document.getElementById('ifr');  
    var targetOrigin = "http://www.google.com";  
    ifr.contentWindow.postMessage('hello world!', targetOrigin);  
};

B頁面通過message事件監(jiān)聽并接受消息:

window.onmessage = function (event) {  
  var data = event.data;//消息  
  var origin = event.origin;//消息來源地址  
  var source = event.source;//源Window對象  
  if(origin=="http://www.baidu.com"){  
console.log(data);//hello world!  
  }  
};

Web Socket

Web Socket目標是在一個單獨的持久連接上提供全雙工、雙向通信(非同源),使用自定義協(xié)議,未加密:ws://,加密:wss://。缺點,使用自定義協(xié)議,時間較長。

1、實例化

var socket=new WebSocket("ws://www.demo.com/server.php")
實例化后,瀏覽器會馬上嘗試創(chuàng)建鏈接。WebSocket也有readyState屬性

0:正在建立連接
1:已經(jīng)建立連接
2:正在關閉鏈接
3:已經(jīng)關閉鏈接

2、發(fā)送和接收數(shù)據(jù)

發(fā)送
只能發(fā)送純文本數(shù)據(jù),對象等數(shù)據(jù)結(jié)構(gòu)需要序列化為JSON字符串。
- 純文本
socket.send('hello')
- 對象

var obj={
  name:'ok'
}
socket.send(JSON.stringify(message))

接受數(shù)據(jù)

socket.onmessage=function(event){
  console.log(event.data)//數(shù)據(jù)在event.data中
}

3、其他事件,在連接的不同生命周期觸發(fā)

open:成功建立連接
error:發(fā)生錯誤時,連接不能持續(xù)
close:連接關閉
必須使用DMO0級定義事件處理程序

4、使用樣例

var socket=new WebSocket("wss://www.demo.html/server.js")

socket.onopen=function(){
    console.log("建立連接")
    socket.send('hello')
}
socket.onmessage=function(event){
    console.log(event.data)
}
socket.onerror=function(){
    console.log("錯誤")
}
socket.onclose=function(){
    console.log("關閉鏈接")
}

Hash跨域

使用場景,當頁面A通過iframe或者frame嵌入了跨域的頁面B,我們可以跨域改變B頁面的hash。改變hash不會刷新頁面,不會請服務器發(fā)送請求。

//A中的代碼
var B=document.getElementByTagName('iframe')[0]
B.src=B.src+'#'+'data'
//B中的代碼
window.onhashchange=function(e){
  var data=window.location.hash
}

安全

一、CSRF

概念

CSRF(Cross-Site Request Forgery)跨站請求偽造

攻擊原理
CSRF防御

(1)驗證 HTTP Referer 字段

根據(jù) HTTP 協(xié)議,在 HTTP 頭中有一個字段叫 Referer,它記錄了該 HTTP 請求的來源地址

(2)在請求地址中添加 token 并驗證

CSRF 攻擊之所以能夠成功,是因為黑客可以完全偽造用戶的請求,該請求中所有的用戶驗證信息都是存在于 cookie 中,因此黑客可以在不知道這些驗證信息的情況下直接利用用戶自己的 cookie 來通過安全驗證。要抵御 CSRF,關鍵在于在請求中放入黑客所不能偽造的信息,并且該信息不存在于 cookie 之中。可以在 HTTP 請求中以參數(shù)的形式加入一個 隨機產(chǎn)生token,并在服務器端建立一個攔截器來驗證這個 token,如果請求中沒有 token 或者 token 內(nèi)容不正確,則認為可能是 CSRF 攻擊而拒絕該請求。

(3)每次請求都要附帶經(jīng)過相應算法計算得到的驗證碼

二、XSS

概念

跨站腳本攻擊(Cross Site Scripting)。

原理

往頁面中 注入惡意script代碼,當代碼被瀏覽器解析執(zhí)行時便達到攻擊的目的。

防御

不能原樣的將用戶輸入的數(shù)據(jù)直接存到服務器,需要對數(shù)據(jù)進行一些處理。

  • 過濾危險的DOM節(jié)點。如具有執(zhí)行腳本能力的script, 具有顯示廣告和色情圖片的img, 具有改變樣式的link, style, 具有內(nèi)嵌頁面的iframe, frame等元素節(jié)點。
  • 過濾危險的屬性節(jié)點。如事件, style, src, href等
  • 對cookie設置httpOnly。

參考資料

JavaScript高級程序設計第三版

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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

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