什么是同源策略椅野?
同源策略是指,瀏覽器出于安全方面的考慮榜旦,只允許與本域下的接口交互幽七。不同源的客戶端腳本在沒有明確授權(quán)的情況下,不能讀寫對方的資源溅呢。
本域包括:
- 同協(xié)議:如都是http或者https
- 同域名:如都是http://jirengu.com/a 和http://jirengu.com/b
- 同端口:如都是8080端口
什么是跨域澡屡?跨域有幾種實現(xiàn)形式?
跨域即在不同的域(協(xié)議咐旧、域名驶鹉、端口中有任意一個不同)之間發(fā)起請求,打破同源策略的限制铣墨,訪問其他域的數(shù)據(jù)室埋。
跨域的實現(xiàn)方式有:
- JSONP
- CORS
- 降域
- postMessage
JSONP
JSONP是JSON的一種使用方式,利用script標(biāo)簽可以跨域引用的特點伊约,對跨域的數(shù)據(jù)進行請求专酗,該方法需要后端數(shù)據(jù)配合尤蛮。
使用JSONP的步驟:
- 本地創(chuàng)建一個數(shù)據(jù)處理函數(shù)func疏叨;
- 利用script標(biāo)簽浅萧,設(shè)置src路徑為向服務(wù)器發(fā)送請求的端口,同時加上參數(shù)callBack = func超埋,用于提供后端反饋數(shù)據(jù)的接口搏讶;
- 服務(wù)端在收到請求后,解析參數(shù)霍殴,計算返還數(shù)據(jù)窍蓝,輸出 fun(data) 字符串。
- fun(data)會放到script標(biāo)簽做為js執(zhí)行繁成。此時會調(diào)用fun函數(shù),將data做為參數(shù)淑玫。
演示舉例:
-
修改hosts文件巾腕,模擬跨域請求失斆婢Α;
-
HTML中尊搬,通過查詢城市名字叁鉴,獲取對應(yīng)天氣及圖片
-
js中請求為:
-
瀏覽器訪問http://a.com:8080
形成跨域,從控制臺看到佛寿,響應(yīng)到了瀏覽器之后不被允許
2.使用JSONP修改js請求
-
客戶端用script向后端發(fā)送請求
-
服務(wù)端響應(yīng)幌墓,提供對應(yīng)城市的天氣和圖片
-
效果,訪問a.com冀泻,請求127.0.0.1
CORS
CORS 全稱是跨域資源共享(Cross-Origin Resource Sharing)常侣,是一種 ajax 跨域請求資源的方式,支持現(xiàn)代瀏覽器弹渔,IE支持10以上胳施。
瀏覽器將CORS請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。
簡單請求
只要同時滿足以下兩大條件肢专,就屬于簡單請求舞肆。
(1) 請求方法是以下三種方法之一:
HEAD
GET
POST
(2)HTTP的頭信息不超出以下幾種字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三個值application/x-www-form-urlencoded、multipart/form-data博杖、text/plain
瀏覽器請求
對于簡單請求椿胯,瀏覽器直接發(fā)出CORS請求。具體來說剃根,就是在頭信息之中哩盲,增加一個Origin字段。
服務(wù)器回應(yīng)
如果Origin指定的源跟继,不在許可范圍內(nèi)种冬,服務(wù)器會返回一個正常的HTTP回應(yīng)。瀏覽器發(fā)現(xiàn)舔糖,這個回應(yīng)的頭信息沒有包含Access-Control-Allow-Origin字段(詳見下文)娱两,就知道出錯了,從而拋出一個錯誤金吗,被XMLHttpRequest的onerror回調(diào)函數(shù)捕獲十兢。注意,這種錯誤無法通過狀態(tài)碼識別摇庙,因為HTTP回應(yīng)的狀態(tài)碼有可能是200旱物。
如果Origin指定的域名在許可范圍內(nèi),服務(wù)器返回的響應(yīng)卫袒,會多出幾個頭信息字段宵呛。
Access-Control-Allow-Origin,該字段是必須的夕凝。它的值要么是請求時Origin字段的值宝穗,要么是一個*户秤,表示接受任意域名的請求。
非簡單請求
非簡單請求是那種對服務(wù)器有特殊要求的請求逮矛,比如請求方法是PUT或DELETE鸡号,或者Content-Type字段的類型是application/json。
瀏覽器請求
非簡單請求的CORS請求须鼎,會在正式通信之前鲸伴,增加一次HTTP查詢請求,稱為"預(yù)檢"請求(preflight)晋控。
瀏覽器先詢問服務(wù)器汞窗,當(dāng)前網(wǎng)頁所在的域名是否在服務(wù)器的許可名單之中,以及可以使用哪些HTTP動詞和頭信息字段糖荒。只有得到肯定答復(fù)杉辙,瀏覽器才會發(fā)出正式的XMLHttpRequest請求,否則就報錯捶朵。
上面代碼中蜘矢,HTTP請求的方法是PUT,并且發(fā)送一個自定義頭信息X-Custom-Header综看。
瀏覽器發(fā)現(xiàn)品腹,這是一個非簡單請求,就自動發(fā)出一個"預(yù)檢"請求红碑,要求服務(wù)器確認可以這樣請求舞吭。下面是這個"預(yù)檢"請求的HTTP頭信息。
服務(wù)器回應(yīng)
服務(wù)器收到"預(yù)檢"請求以后析珊,檢查了Origin羡鸥、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,確認允許跨源請求忠寻,就可以做出回應(yīng)惧浴。
上面的HTTP回應(yīng)中,關(guān)鍵的是Access-Control-Allow-Origin字段奕剃,表示http://api.bob.com可以請求數(shù)據(jù)衷旅。該字段也可以設(shè)為星號,表示同意任意跨源請求纵朋。
Access-Control-Allow-Methods柿顶,該字段必需,它的值是逗號分隔的一個字符串操软,表明服務(wù)器支持的所有跨域請求的方法嘁锯。注意,返回的是所有支持的方法猪钮,而不單是瀏覽器請求的那個方法。這是為了避免多次"預(yù)檢"請求
如果瀏覽器否定了"預(yù)檢"請求,會返回一個正常的HTTP回應(yīng)笆载,但是沒有任何CORS相關(guān)的頭信息字段扑馁。這時,瀏覽器就會認定腻要,服務(wù)器不同意預(yù)檢請求涝登,因此觸發(fā)一個錯誤雄家,被XMLHttpRequest對象的onerror回調(diào)函數(shù)捕獲≌凸觯控制臺會打印出如下的報錯信息。
說明:以上參考引用了跨域資源共享 CORS 詳解 ---by 阮一峰
演示舉例
客戶端請求:
服務(wù)器端響應(yīng):
效果:
訪問a.com,請求為127.0.0.1媳纬,實現(xiàn)跨域
降域
瀏覽器內(nèi)部做了限制施掏,只有同域名下的頁面才能用JS去獲取和操作iframe頁面,否則只能加載素挽,但不能用外部JS去獲取和操作
通過設(shè)置document.domain的方式抖苦,將兩個域名的domain設(shè)置為一個锌历,如對于a.test.com和b.test.com,可以通過js設(shè)置document.domain = "test.com"究西,實現(xiàn)跨域
演示舉例:
a.test.com:8080下的a.html內(nèi)嵌了一個iframe,src為http://b.test.com:8080/b.html
正常情況下峦失,因為a,b不同源术吗,a不能修改內(nèi)嵌的iframe中輸入框的內(nèi)容;
但通過降域:document.domain = 'test.com';使得a可以修改內(nèi)嵌的b中的內(nèi)容
postMessage
postMessage是一個web API,可以實現(xiàn)跨域通信隘蝎。window.postMessage()被調(diào)用時,會在所有頁面腳本執(zhí)行完畢后狮含,向目標(biāo)窗口派發(fā)一個MessageEvent消息。語法如下:
otherWindow.postMessage(message, targetOrigin, [transfer]);
- otherwindow
其他窗口的一個引用几迄,比如iframe的contentWindow屬性拴测、執(zhí)行window.open返回的窗口對象集索、或者是命名過或數(shù)值索引的window.frames。 - message
將要發(fā)送到其他 window的數(shù)據(jù)妆距。它將會被結(jié)構(gòu)化克隆算法序列化函匕。這意味著你可以不受什么限制的將數(shù)據(jù)對象安全的傳送給目標(biāo)窗口而無需自己序列化盅惜。 - targetOrigin
通過窗口的origin屬性來指定哪些窗口能接收到消息事件,其值可以是字符串*(表示無限制)或者一個URI结啼。在發(fā)送消息的時候屈芜,如果目標(biāo)窗口的協(xié)議、主機地址或端口這三者的任意一項不匹配targetOrigin提供的值属铁,那么消息就不會被發(fā)送焦蘑;只有三者完全匹配,消息才會被發(fā)送坟乾。這個機制用來控制消息可以發(fā)送到哪些窗口蝶防;例如间学,當(dāng)用postMessage傳送密碼時印荔,這個參數(shù)就顯得尤為重要,必須保證它的值與這條包含密碼的信息的預(yù)期接受者的orign屬性完全一致嘿悬,來防止密碼被惡意的第三方截獲水泉。如果你明確的知道消息應(yīng)該發(fā)送到哪個窗口草则,那么請始終提供一個有確切值的targetOrigin,而不是*源内。不提供確切的目標(biāo)將導(dǎo)致數(shù)據(jù)泄露到任何對數(shù)據(jù)感興趣的惡意站點份殿。 - transfer(可選)
是一串和message 同時傳遞的Transferable對象. 這些對象的所有權(quán)將被轉(zhuǎn)移給消息的接收方卿嘲,而發(fā)送一方將不再保有所有權(quán)
message 的屬性有:
- data
從其他 window 中傳遞過來的對象 - origin
調(diào)用postMessage時消息發(fā)送方窗口的origin,這個字符串由 協(xié)議焚鲜、“://“、域名糯彬、“ : 端口號”拼接而成撩扒。請注意吨些,這個origin不能保證是該窗口的當(dāng)前或未來origin,因為postMessage被調(diào)用后可能被導(dǎo)航到不同的位置泉手。 - source
對發(fā)送消息的窗口對象的引用; 您可以使用此來在具有不同origin的兩個窗口之間建立雙向通信
安全問題:
如果不希望從其他網(wǎng)站接收message斩萌,請不要為message事件添加任何事件偵聽器屏轰。 這是一個完全萬無一失的方式來避免安全問題。
如果確實希望從其他網(wǎng)站接收message姆吭,請始終使用origin和source屬性驗證發(fā)件人的身份内狸。 任何窗口(包括例如http://evil.example.com)都可以向任何其他窗口發(fā)送消息升敲,并且不能保證未知發(fā)件人不會發(fā)送惡意消息驴党。 但是,驗證身份后倔既,仍然應(yīng)該始終驗證接收到的消息的語法鹏氧。 否則,信任只發(fā)送受信任郵件的網(wǎng)站中的安全漏洞可能會在網(wǎng)站中打開跨網(wǎng)站腳本漏洞实蓬。
當(dāng)使用postMessage將數(shù)據(jù)發(fā)送到其他窗口時安皱,始終指定精確的目標(biāo)origin,而不是*腾窝。 惡意網(wǎng)站可以在您不知情的情況下更改窗口的位置居砖,因此它可以攔截使用postMessage發(fā)送的數(shù)據(jù)奏候。
說明:以上參考引用了MDN:window.postMessage
演示舉例
a中內(nèi)嵌b的iframe,同時向http://b.test.com:8080發(fā)送post消息暇榴;
并監(jiān)聽post消息,確認origin來自b婆硬,才使用消息內(nèi)容彬犯;
b中做同樣處理,向a發(fā)送postmessage湖蜕,同時監(jiān)聽消息昭抒,確認來源為a
效果: