什么是跨域
瀏覽器出于安全方面的考慮,只允許客戶端與本域(同協(xié)議萌朱、同域名、同端口策菜,三者缺一不可)下的接口交互晶疼。不同源的客戶端腳本在沒(méi)有明確授權(quán)的情況下,不能讀寫對(duì)方的資源又憨,這被稱為同源策略翠霍。
同協(xié)議:如都是http或者h(yuǎn)ttps
同域名:如都是http://xxx.com/a和http://xxx.com/b
同端口:如都是8080端口
而有時(shí)候,我們不得不在一個(gè)客戶端下訪問(wèn)不同域中的資源蠢莺,于是需要用到一些方法來(lái)避開瀏覽器的同源策略寒匙,這些方法被稱為跨域。
實(shí)現(xiàn)跨域有如下幾種方法:
1. JSONP
JSONP(JSON with Padding)是數(shù)據(jù)格式JSON的一種使用模式躏将,可以使網(wǎng)頁(yè)實(shí)現(xiàn)跨域請(qǐng)求锄弱。其原理主要利用了 HTML
的script
標(biāo)簽。由于script
是采用開放策略祸憋,通過(guò)設(shè)置src
引入不同域下的資源会宪,所以可以通過(guò)script
實(shí)現(xiàn)跨域,該方法需要后端支持蚯窥。jsonp跨域的實(shí)現(xiàn)步驟如下:
- 首先客戶端定義一個(gè)數(shù)據(jù)處理函數(shù)
fun
掸鹅,用于處理后端服務(wù)器返回的數(shù)據(jù)喜命。 - 創(chuàng)建一個(gè)
script
標(biāo)簽,并將script
的src
設(shè)置為后端接口河劝,并在最后加一個(gè)參數(shù)callback=fun
壁榕。 - 服務(wù)端在收到由
script
標(biāo)簽發(fā)出的請(qǐng)求后,會(huì)解析請(qǐng)求參數(shù)赎瞎,獲取callback
對(duì)應(yīng)的fun
的字符串牌里,然后將要返回的數(shù)據(jù)data
與fun
拼接為一個(gè)'fun(data)'
的字符串,返回客戶端务甥。 - 客戶端拿到響應(yīng)后會(huì)放到
script
標(biāo)簽里執(zhí)行牡辽,此時(shí)會(huì)調(diào)用fun
函數(shù),參數(shù)為data
敞临。
下面來(lái)做個(gè)演示态辛,首先為演示方便,將系統(tǒng)的hosts做如下修改:
127.0.0.1 example.a.com
127.0.0.1 example.b.com
服務(wù)器端(用server-mock啟動(dòng)挺尿,保存圖片的url地址數(shù)據(jù)):
客戶端(展示隨機(jī)圖片):
請(qǐng)求結(jié)果:
以上例子最終實(shí)現(xiàn)了由example.a.com到example.b.com的跨域奏黑。應(yīng)注意的是,因?yàn)?code><script>只能發(fā)送GET請(qǐng)求编矾,所以jsonp只能實(shí)現(xiàn)GET請(qǐng)求的跨域熟史。如果希望能實(shí)現(xiàn)其他請(qǐng)求的跨域,就可以用接下來(lái)介紹的一種方法——CORS窄俏。
2.CORS
CORS(全稱為:Cross-Origin Resouce Sharing)跨域資源共享蹂匹,是一種通過(guò)ajax跨域請(qǐng)求資源的方法。瀏覽器將CORS請(qǐng)求分為兩大類凹蜈,簡(jiǎn)單請(qǐng)求(simple request)和非簡(jiǎn)單請(qǐng)求(not-so-simple request限寞,瀏覽器對(duì)這兩種請(qǐng)求的處理方式不一樣。如果請(qǐng)求滿足以下兩個(gè)條件仰坦,則為簡(jiǎn)單請(qǐng)求履植。
- 請(qǐng)求方式為HEAD,GET缎岗,POST三者中第一個(gè)静尼。
- HTTP頭部信息不超過(guò)以下幾種字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三個(gè)值application/x-www-form-urlencoded、multipart/form-data传泊、text/plain鼠渺。
簡(jiǎn)單請(qǐng)求的實(shí)現(xiàn)方式即當(dāng)用XMLHttpRequest發(fā)請(qǐng)求時(shí),瀏覽器如果發(fā)現(xiàn)該請(qǐng)求不符合同源策略眷细,會(huì)給該請(qǐng)求加上一個(gè)請(qǐng)求頭origin拦盹,origin用來(lái)說(shuō)明本次請(qǐng)求來(lái)自哪個(gè)源(協(xié)議+域名+端口)。如果origin指定的源不在后臺(tái)允許范圍內(nèi)溪椎,后臺(tái)會(huì)返回一個(gè)正常的HTTP響應(yīng)普舆,然后瀏覽器會(huì)發(fā)現(xiàn)該響應(yīng)頭部信息不包含Access-Control-Allow-Origin字段恬口,然后拋出一個(gè)錯(cuò)誤,該錯(cuò)誤被XMLHttpRequest的onerror函數(shù)捕獲沼侣,響應(yīng)被駁回祖能,但因?yàn)樵撳e(cuò)誤無(wú)法通過(guò)狀態(tài)碼識(shí)別,所以HTTP回應(yīng)的狀態(tài)碼還是200蛾洛。如果origin在后臺(tái)允許范圍內(nèi)养铸,則服務(wù)器返回的響應(yīng),會(huì)包含Access-Control-Allow-Origin:Origin(指定的源)信息轧膘,瀏覽器此時(shí)不會(huì)拋錯(cuò)钞螟,響應(yīng)能正常處理。
非簡(jiǎn)單請(qǐng)求是是請(qǐng)求方法為PUT或DELETE谎碍,又或者Content-Type為application/json的對(duì)服務(wù)器有特殊要求的請(qǐng)求鳞滨。非簡(jiǎn)單請(qǐng)求的CORS請(qǐng)求,會(huì)在正式通信前增加一次HTTP查詢蟆淀,稱為預(yù)檢(preflight)拯啦,詢問(wèn)服務(wù)器當(dāng)前網(wǎng)頁(yè)所在域名是否在服務(wù)器的許可名單中,如果在扳碍,則發(fā)出正式的XMLHttpRequest提岔,之后就與簡(jiǎn)單請(qǐng)求一樣,不在則報(bào)錯(cuò)笋敞。
依舊用上面的例子。
服務(wù)器端(設(shè)置一個(gè)響應(yīng)頭荠瘪,里面包含了允許請(qǐng)求的域名信息)
客戶端(用ajax發(fā)請(qǐng)求)
最終實(shí)現(xiàn)的效果與第一個(gè)jsonp的例子一樣夯巷。
3.降域
還有一種方式,就是通過(guò)降域來(lái)實(shí)現(xiàn)跨域哀墓。即通過(guò)設(shè)置document.domain的方式趁餐,將兩個(gè)域名的domain設(shè)置為一個(gè),如對(duì)于a.example.com和b.example.com篮绰,可以通過(guò)js設(shè)置document.domain = "example.com"
后雷,實(shí)現(xiàn)跨域。
做個(gè)演示吠各,假設(shè)在http://a.example.com:8080下有一個(gè)a.html文件臀突,其中a.html中有一個(gè)iframe
,它的src
為http://b.example.com:8080/b.html贾漏。
正常情況下候学,由于a.html,b.html不同源纵散,他們之間無(wú)法正常通信梳码,但在設(shè)置
document.domain = "example.com"
后隐圾,兩邊可以互相通信。最終效果如下:
用降域方法實(shí)現(xiàn)跨域操作簡(jiǎn)單掰茶,但是有一些缺點(diǎn)暇藏。比如域名只能往下設(shè)置,不能回去濒蒋,比如從example.com回到a.example.com盐碱。同時(shí)如果一個(gè)子域名被攻擊,多個(gè)被降域的域名都會(huì)被連帶攻擊啊胶,有很大的安全風(fēng)險(xiǎn)甸各。
4.postMessage
postMessage是一個(gè)web API,可以實(shí)現(xiàn)跨域通信焰坪。window.postMessage()
被調(diào)用時(shí)趣倾,會(huì)在所有頁(yè)面腳本執(zhí)行完畢后,向目標(biāo)窗口派發(fā)一個(gè)MessageEvent
消息某饰。語(yǔ)法如下:
otherWindow.postMessage(message, targetOrigin, [transfer]);
-
otherWindow
表示目標(biāo)窗口的一個(gè)引用儒恋,比如iframe
的contentWindow
屬性、執(zhí)行window.open
返回的窗口對(duì)象黔漂、或者是命名過(guò)或通過(guò)數(shù)值索引的window.frames
诫尽。 -
message
表示需要發(fā)送到目標(biāo)窗口的數(shù)據(jù)。 -
targetOrigin
決定了哪些窗口可以接收消息炬守,其值可以是字符串"*"(表示無(wú)限制)或者一個(gè)URI牧嫉。 -
transfer
是一串和message同時(shí)傳遞的Transferable 對(duì)象,這些對(duì)象的所有權(quán)將被轉(zhuǎn)移給消息的接收方减途,而發(fā)送一方將不再保有所有權(quán)酣藻。
MessageEvent
具有如下屬性:
-
message
屬性表示該message的類型。 - data屬性為
window.postMessage
的第一個(gè)參數(shù)鳍置; - origin 屬性表示調(diào)用
window.postMessage()
方法時(shí)調(diào)用頁(yè)面的當(dāng)前狀態(tài)辽剧; - source 屬性記錄調(diào)用
window.postMessage()
方法的窗口信息。
用一個(gè)與上面降域類似的例子來(lái)做演示税产。同樣有兩個(gè)頁(yè)面a.html和b.html怕轿,a.html中的iframe
的src
指向b.html。
最終實(shí)現(xiàn)a.html與b.html通信效果如下:
使用postMessage方法應(yīng)注意的是辟拷,如果不希望從其他網(wǎng)站接收message撞羽,那么不要為message事件添加任何監(jiān)聽(tīng)器。如果確實(shí)希望接收其他網(wǎng)站的message梧兼,那么應(yīng)該始終使用origin和source屬性來(lái)驗(yàn)證發(fā)件人的身份放吩,以免被惡意的網(wǎng)站攻擊。
小結(jié)
以上就是幾種常見(jiàn)的跨域方法羽杰,各有優(yōu)劣渡紫,且各自都有一定的安全問(wèn)題到推,在日常應(yīng)用中,需要有針對(duì)性的使用惕澎,對(duì)可能的安全風(fēng)險(xiǎn)采取相應(yīng)措施莉测。