跨域技術與安全

CORS

一、CORS內部機制與實現

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

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

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

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

整個CORS通信過程,都是瀏覽器自動完成,不需要用戶參與。對于開發者來說,CORS通信與同源的AJAX通信沒有差別,代碼完全一樣。瀏覽器一旦發現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.基礎信息
簡單請求,瀏覽器直接發出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...

服務器根據Origin的值來決定是否同意請求。如果Origin指定的源,不在許可范圍內,服務器會返回一個正常的HTTP響應。瀏覽器發現,這個回應的頭信息沒有包含Access-Control-Allow-Origin字段,就知道出錯了,從而拋出一個錯誤,被XMLHttpRequestonerror回調函數捕獲。注意,這種錯誤無法通過狀態碼識別,因為HTTP回應的狀態碼有可能是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
該字段可選。它的值是一個布爾值,表示是否允許發送Cookie。默認情況下,Cookie不包括在CORS請求之中。設為true,即表示服務器明確許可,Cookie可以包含在請求中,一起發給服務器。這個值也只能設為true,如果服務器不要瀏覽器發送Cookie,刪除該字段即可。
(3)Access-Control-Expose-Headers
該字段可選。CORS請求時,XMLHttpRequest對象的getResponseHeader()方法只能拿到6個基本字段:Cache-Control、Content-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他字段,就必須在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值。
2.withCredentials 屬性
CORS請求默認不發送Cookie和HTTP認證信息。如果要把Cookie發到服務器,一方面要服務器同意,指定Access-Control-Allow-Credentials字段。

Access-Control-Allow-Credentials:true

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

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

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

xhr.withCredentials = false;

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

四、非簡單請求

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

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

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請求會額外發送的頭信息字段,上例是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可以請求數據。該字段也可以設為星號,表示同意任意跨源請求。
如果瀏覽器否定了預檢請求,返回一個正常的HTTP回應,但是么有任何CROS相關的頭信息字段。這時,瀏覽器就會認定,服務器不同意預檢請求,因此觸發一個錯誤,被XMLHttpRequest對象的onerror回調函數捕獲。
服務器回應的其他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
該字段可選,用來指定本次預檢請求的有效期,單位為秒。上面結果中,有效期是20天(1728000秒),即允許緩存該條回應1728000秒(即20天),在此期間,不用發出另一條預檢請求。
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實現

所有瀏覽器都支持簡單請求(非預檢請求和不帶憑據的請求),因此有必要做跨瀏覽器方案。檢測XHR是否支持CORS的最簡單方式就是就是檢查xhr中是否存在withCredentials屬性。再結合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);//沒有第三個參數     
  }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的縮寫,通過創建script元素來使用,讓其src屬性為一個跨域的URL,在URL后面添加響應后的回調函數,響應的數據以參數的形式傳入回調函數
2、如何使用jsonp跨域

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

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

圖像Ping

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

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

修改document.domain跨子域通信

1、適用情況
當某個頁面中包含其他子域的框架或者內嵌框架時,可以通過將每個頁面的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)方法,兩個參數,第一個是消息,第二個是把消息傳給誰。接收到XDM消息后,觸發windowonmessage事件。
2、如何用
A頁面通過postMessage方法發送消息:

//支持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事件監聽并接受消息:

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

1、實例化

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

0:正在建立連接
1:已經建立連接
2:正在關閉鏈接
3:已經關閉鏈接

2、發送和接收數據

發送
只能發送純文本數據,對象等數據結構需要序列化為JSON字符串。
- 純文本
socket.send('hello')
- 對象

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

接受數據

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

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

open:成功建立連接
error:發生錯誤時,連接不能持續
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不會刷新頁面,不會請服務器發送請求。

//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 字段

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

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

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

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

二、XSS

概念

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

原理

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

防御

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

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

參考資料

JavaScript高級程序設計第三版

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,739評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,634評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,653評論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,063評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,835評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,235評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,459評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,000評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,819評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,004評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,257評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,676評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,937評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,717評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,003評論 2 374

推薦閱讀更多精彩內容