前言:對(duì)于跨域請(qǐng)求,很早之前就有去了解過(guò),但因?yàn)橐恢标P(guān)注的都是服務(wù)器后端開(kāi)發(fā),故也就僅僅停留在概念的理解上而沒(méi)有機(jī)會(huì)在實(shí)際開(kāi)發(fā)場(chǎng)景中接觸得到。最近在公司的開(kāi)發(fā)任務(wù)中,需要接觸到 Ajax 跨域請(qǐng)求,由于之前沒(méi)有遇到過(guò)類(lèi)似的問(wèn)題,在開(kāi)發(fā)過(guò)程中遇到不少困難,也查閱了不少資料和博客。在這過(guò)程中收獲了不少,故特意寫(xiě)下以下文章總結(jié),如果文章有什么不足之處,還望各位指出。
什么是跨域請(qǐng)求
概述
在 HTML 中,<a>, <form>, <img>, <script>, <iframe>, <link> 等標(biāo)簽以及 Ajax 都可以指向一個(gè)資源地址,而所謂的跨域請(qǐng)求就是指:當(dāng)前發(fā)起請(qǐng)求的域與該請(qǐng)求指向的資源所在的域不一樣。這里的域指的是這樣的一個(gè)概念:我們認(rèn)為若協(xié)議 + 域名 + 端口號(hào)均相同,那么就是同域。
舉個(gè)例子:假如一個(gè)域名為aaa.cn的網(wǎng)站,它發(fā)起一個(gè)資源路徑為aaa.cn/books/getBookInfo的 Ajax 請(qǐng)求,那么這個(gè)請(qǐng)求是同域的,因?yàn)橘Y源路徑的協(xié)議、域名以及端口號(hào)與當(dāng)前域一致(例子中協(xié)議名默認(rèn)為http,端口號(hào)默認(rèn)為80)。但是,如果發(fā)起一個(gè)資源路徑為bbb.com/pay/purchase的 Ajax 請(qǐng)求,那么這個(gè)請(qǐng)求就是跨域請(qǐng)求,因?yàn)橛虿灰恢?,與此同時(shí)由于安全問(wèn)題,這種請(qǐng)求會(huì)受到同源策略限制。
跨域請(qǐng)求的安全問(wèn)題
通常,瀏覽器會(huì)對(duì)上面提到的跨域請(qǐng)求作出限制。瀏覽器之所以要對(duì)跨域請(qǐng)求作出限制,是出于安全方面的考慮,因?yàn)榭缬蛘?qǐng)求有可能被不法分子利用來(lái)發(fā)動(dòng) CSRF攻擊。
CSRF攻擊:
CSRF(Cross-site request forgery),中文名稱(chēng):跨站請(qǐng)求偽造,也被稱(chēng)為:one click attack/session riding,縮寫(xiě)為:CSRF/XSRF。CSRF攻擊者在用戶(hù)已經(jīng)登錄目標(biāo)網(wǎng)站之后,誘使用戶(hù)訪問(wèn)一個(gè)攻擊頁(yè)面,利用目標(biāo)網(wǎng)站對(duì)用戶(hù)的信任,以用戶(hù)身份在攻擊頁(yè)面對(duì)目標(biāo)網(wǎng)站發(fā)起偽造用戶(hù)操作的請(qǐng)求,達(dá)到攻擊目的。
CSRF 攻擊的原理大致描述如下:有兩個(gè)網(wǎng)站,其中A網(wǎng)站是真實(shí)受信任的網(wǎng)站,而B(niǎo)網(wǎng)站是危險(xiǎn)網(wǎng)站。在用戶(hù)登陸了受信任的A網(wǎng)站是,本地會(huì)存儲(chǔ)A網(wǎng)站相關(guān)的Cookie,并且瀏覽器也維護(hù)這一個(gè)Session會(huì)話(huà)。這時(shí),如果用戶(hù)在沒(méi)有登出A網(wǎng)站的情況下訪問(wèn)危險(xiǎn)網(wǎng)站B,那么危險(xiǎn)網(wǎng)站B就可以模擬發(fā)出一個(gè)對(duì)A網(wǎng)站的請(qǐng)求(跨域請(qǐng)求)對(duì)A網(wǎng)站進(jìn)行操作,而在A網(wǎng)站的角度來(lái)看是并不知道請(qǐng)求是由B網(wǎng)站發(fā)出來(lái)的(Session和Cookie均為A網(wǎng)站的),這時(shí)便成功發(fā)動(dòng)一次CSRF 攻擊。
因而 CSRF 攻擊可以簡(jiǎn)單理解為:攻擊者盜用了你的身份,以你的名義發(fā)送而已請(qǐng)求。CSRF能夠做的事情包括:以你名義發(fā)送郵件,發(fā)消息,盜取你的賬號(hào),甚至于購(gòu)買(mǎi)商品,虛擬貨幣轉(zhuǎn)賬......造成的問(wèn)題包括:個(gè)人隱私泄露以及財(cái)產(chǎn)安全。
因此,大多數(shù)瀏覽器都會(huì)跨域請(qǐng)求作出限制,這是從瀏覽器層面上的對(duì) CSRF 攻擊的一種防御,但是需要注意的是在復(fù)雜的網(wǎng)絡(luò)環(huán)境中借助瀏覽器來(lái)防御 CSRF 攻擊并不足夠,還需要從服務(wù)端或者客戶(hù)端方面入手防御。
同源策略(Same-origin Policy)
概述
· 同源策略是 Netscape 提出的一個(gè)著名的安全策略
· 同源策略是瀏覽器最核心最基礎(chǔ)的安全策略
· 現(xiàn)在所有的可支持 Javascript 的瀏覽器都會(huì)使用這個(gè)策略
· web構(gòu)建在同源策略基礎(chǔ)之上,瀏覽器對(duì)非同源腳本的限制措施是對(duì)同源策略的具體實(shí)現(xiàn)
同源策略的含義
· DOM 層面的同源策略:限制了來(lái)自不同源的”Document”對(duì)象或 JS 腳本,對(duì)當(dāng)前“document”對(duì)象的讀取或設(shè)置某些屬性
· Cookie和XMLHttprequest層面的同源策略:禁止 Ajax 直接發(fā)起跨域HTTP請(qǐng)求(其實(shí)可以發(fā)送請(qǐng)求,結(jié)果被瀏覽器攔截,不展示),同時(shí) Ajax 請(qǐng)求不能攜帶與本網(wǎng)站不同源的 Cookie。
· 同源策略的非絕對(duì)性:<script><img><iframe><link><video><audio>等帶有src屬性的標(biāo)簽可以從不同的域加載和執(zhí)行資源。
· 其他插件的同源策略:flash、java applet、silverlight、googlegears等瀏覽器加載的第三方插件也有各自的同源策略,只是這些同源策略不屬于瀏覽器原生的同源策略,如果有漏洞則可能被黑客利用,從而留下XSS攻擊的后患
同源的具體含義
· 域名、協(xié)議、端口有一個(gè)不同就不是同源,三者均相同,這兩個(gè)網(wǎng)站才是同源
跨域解決方法
雖然在安全層面上同源限制是必要的,但有時(shí)同源策略會(huì)對(duì)我們的合理用途造成影響,為了避免開(kāi)發(fā)的應(yīng)用受到限制,有多種方式可以繞開(kāi)同源策略,下面介紹的是經(jīng)常使用的 JSONP, CORS 方法。
JSONP
原理:
· JSONP 是一種非官方的跨域數(shù)據(jù)交互協(xié)議
· JSONP 本質(zhì)上是利用 <script><img><iframe> 等標(biāo)簽不受同源策略限制,可以從不同域加載并執(zhí)行資源的特性,來(lái)實(shí)現(xiàn)數(shù)據(jù)跨域傳輸。
· JSONP由兩部分組成:回調(diào)函數(shù)和數(shù)據(jù)。回調(diào)函數(shù)是當(dāng)響應(yīng)到來(lái)時(shí)應(yīng)該在頁(yè)面中調(diào)用的函數(shù),而數(shù)據(jù)就是傳入回調(diào)函數(shù)中的JSON數(shù)據(jù)。
· JSONP 的理念就是,與服務(wù)端約定好一個(gè)回調(diào)函數(shù)名,服務(wù)端接收到請(qǐng)求后,將返回一段 Javascript,在這段 Javascript 代碼中調(diào)用了約定好的回調(diào)函數(shù),并且將數(shù)據(jù)作為參數(shù)進(jìn)行傳遞。當(dāng)網(wǎng)頁(yè)接收到這段 Javascript 代碼后,就會(huì)執(zhí)行這個(gè)回調(diào)函數(shù),這時(shí)數(shù)據(jù)已經(jīng)成功傳輸?shù)娇蛻?hù)端了。
示例:
首先當(dāng)前頁(yè)面中聲明有這樣的一個(gè)函數(shù),它將作為 JSONP 的回調(diào)函數(shù)處理作為函數(shù)參數(shù)傳入的數(shù)據(jù)
然后,我們就可以借助 <script><img><iframe> 等標(biāo)簽可以引入不同域資源的特性,將需要發(fā)送的請(qǐng)求的路徑作為src參數(shù),其中需要注意的是:需要告知服務(wù)端回調(diào)函數(shù)的函數(shù)名。
這時(shí)服務(wù)端在返回?cái)?shù)據(jù)的時(shí)候,就會(huì)返回一端 Javascript 代碼,在 Javascript代碼中調(diào)用了回調(diào)函數(shù),并且需要返回的數(shù)據(jù)作為回調(diào)函數(shù)的參數(shù)
最后頁(yè)面成功加載了剛才指定路徑的資源后,將會(huì)執(zhí)行該 Javascript 代碼,dosomething函數(shù)將執(zhí)行,這時(shí)一次跨域請(qǐng)求完成。
另外,如果頁(yè)面引入了 jQuery,那么可以通過(guò)它封裝的方法很方便的實(shí)現(xiàn)JSONP操作了
優(yōu)缺點(diǎn):
JSONP 的優(yōu)點(diǎn)是:它不像XMLHttpRequest對(duì)象實(shí)現(xiàn)的Ajax請(qǐng)求那樣受到同源策略的限制;它的兼容性更好,在更加古老的瀏覽器中都可以運(yùn)行。
JSONP 的缺點(diǎn)是:它只支持 GET 請(qǐng)求,而不支持 POST 請(qǐng)求等其他類(lèi)型的 HTTP 請(qǐng)求
CORS
介紹
跨源資源共享 Cross-Origin Resource Sharing(CORS) 是一個(gè)新的 W3C 標(biāo)準(zhǔn),它新增的一組HTTP首部字段,允許服務(wù)端其聲明哪些源站有權(quán)限訪問(wèn)哪些資源。換言之,它允許瀏覽器向聲明了 CORS 的跨域服務(wù)器,發(fā)出 XMLHttpReuest 請(qǐng)求,從而克服 Ajax 只能同源使用的限制。
另外,規(guī)范也要求對(duì)于非簡(jiǎn)單請(qǐng)求,瀏覽器必須首先使用 OPTION 方法發(fā)起一個(gè)預(yù)檢請(qǐng)求(preflight request),從而獲知服務(wù)端是否允許該跨域請(qǐng)求,在服務(wù)器確定允許后,才發(fā)起實(shí)際的HTTP請(qǐng)求。
HTTP 協(xié)議 Header 簡(jiǎn)析
下面對(duì) CORS 中新增的 HTTP 首部字段進(jìn)行簡(jiǎn)析:
· Access-Control-Allow-Origin
響應(yīng)首部中可以攜帶這個(gè)頭部表示服務(wù)器允許哪些域可以訪問(wèn)該資源,其語(yǔ)法如下:
其中,origin 參數(shù)的值指定了允許訪問(wèn)該資源的外域 URI。對(duì)于不需要攜帶身份憑證的請(qǐng)求,服務(wù)器可以指定該字段的值為通配符,表示允許來(lái)自所有域的請(qǐng)求。
· Access-Control-Allow-Methods
該首部字段用于預(yù)檢請(qǐng)求的響應(yīng),指明實(shí)際請(qǐng)求所允許使用的HTTP方法。其語(yǔ)法如下:
· Access-Control-Allow-Headers
該首部字段用于預(yù)檢請(qǐng)求的響應(yīng)。指明了實(shí)際請(qǐng)求中允許攜帶的首部字段。其語(yǔ)法如下:
· Access-Control-Max-Age
該首部字段用于預(yù)檢請(qǐng)求的響應(yīng),指定了預(yù)檢請(qǐng)求能夠被緩存多久,其語(yǔ)法如下:
· Access-Control-Allow-Credentials
該字段可選。它的值是一個(gè)布爾值,表示是否允許發(fā)送Cookie。默認(rèn)情況下,Cookie不包括在CORS請(qǐng)求之中。設(shè)為true,即表示服務(wù)器明確許可,Cookie可以包含在請(qǐng)求中,一起發(fā)給服務(wù)器。其語(yǔ)法如下:
另外,如果要把 Cookie 發(fā)送到服務(wù)器,除了服務(wù)端要帶上Access-Control-Allow-Credentials首部字段外,另一方面請(qǐng)求中也要帶上withCredentials屬性。
但是需要注意的是:如果需要在 Ajax 中設(shè)置和獲取 Cookie,那么Access-Control-Allow-Origin首部字段不能設(shè)置為* ,必須設(shè)置為具體的 origin 源站。
· Origin
該首部字段表明預(yù)檢請(qǐng)求或?qū)嶋H請(qǐng)求的源站。不管是否為跨域請(qǐng)求,Origin字段總是被發(fā)送。其語(yǔ)法如下:
· Access-Control-Request-Method
該首部字段用于預(yù)檢請(qǐng)求。其作用是,將實(shí)際請(qǐng)求所使用的 HTTP 方法告訴服務(wù)器。其語(yǔ)法如下:
· Access-Control-Request-Headers
該首部字段用于預(yù)檢請(qǐng)求。其作用是,將實(shí)際請(qǐng)求所攜帶的首部字段告訴服務(wù)器。其語(yǔ)法如下:
示例
假設(shè)我們?cè)?/span> bbb.cn 域名下,發(fā)送一個(gè) Ajax 請(qǐng)求到 aaa.cn 域名,其路徑如下:http://aaa.cn/localserver/api/corsTest 。由于同源策略,這樣的 Ajax 請(qǐng)求將會(huì)被瀏覽器所攔截,得到下面的信息:
若想能夠發(fā)送跨域請(qǐng)求,我們只需要在服務(wù)器的響應(yīng)中配置適當(dāng)?shù)?/span>CORS HTTP 首部字段就可以了,例如可以加入以下的首部字段:
此時(shí),Ajax請(qǐng)求就可以順利的發(fā)送和接收了,對(duì)于在 Java Web 項(xiàng)目中,如何在 Servlet 或這 Spring MVC 中配置 CORS
與 JSONP 的比較
· JSONP 只能實(shí)現(xiàn) GET 請(qǐng)求,而 CORS 支持所有類(lèi)型的 HTTP 請(qǐng)求
· 使用 CORS ,開(kāi)發(fā)者可以是使用普通的 XMLHttpRequest 發(fā)起請(qǐng)求和獲取數(shù)據(jù),比起 JSONP 有更好的錯(cuò)誤處理
· 雖然絕大多數(shù)現(xiàn)代的瀏覽器都已經(jīng)支持 CORS,但是 CORS 的兼容性比不上 JSONP,一些比較老的瀏覽器只支持 JSONP