摘要:一些技術(shù)都默認(rèn)采取了同源策略,這些技術(shù)范圍包括但不限于。但是相比較以上的各種場景和繞過同源策略的方法,的跨域請(qǐng)求設(shè)置很容易,只需要在目標(biāo)服務(wù)的根目錄下
在前端開發(fā)的過程中,我們經(jīng)常遇到"跨域"的問題,以下的文章將列舉一下我在工作中碰到的跨域問題。
以及稍稍的探討一下為什么會(huì)有"跨域"問題的出現(xiàn),和所謂的"同源策略"
1995 年由 Netscape 公司提出,之后被其他瀏覽器廠商采納。
同源策略只是一個(gè)規(guī)范,并沒有指定其具體的使用范圍和實(shí)現(xiàn)方式,各個(gè)瀏覽器廠商都針對(duì)同源策略做了自己的實(shí)現(xiàn)。
一些 web 技術(shù)都默認(rèn)采取了同源策略,這些技術(shù)范圍包括但不限于Silverlight, Adobe Flash, Adobe Acrobat, Dom, XMLHttpRequest。
2. 定義Under the policy, a web browser permits scripts contained in a first web page to access data in a second web page, but only if both web pages have the same origin. An origin is defined as a combination of URI scheme, hostname, and port number.
判斷同源的三個(gè)要素:
相同的協(xié)議
相同的域名
相同的端口號(hào)
3. 存在的意義為了保證使用者信息的安全,防止惡意網(wǎng)站篡改用戶數(shù)據(jù)
舉個(gè)例子:
假設(shè)沒有同源策略,那么我在A網(wǎng)站下的cookie就可以被任何一個(gè)網(wǎng)站拿到;那么這個(gè)網(wǎng)站的所有者,就可以使用我的cookie(也就是我的身份)在A網(wǎng)站下進(jìn)行操作。
同源策略可以算是 web 前端安全的基石,如果缺少同源策略,瀏覽器也就沒有了安全性可言。
4. 限制范圍非同源的網(wǎng)站之間
無法共享 cookie, localStorage, indexDB
無法操作彼此的 dom 元素
無法發(fā)送 ajax 請(qǐng)求
無法通過 flash 發(fā)送 http 請(qǐng)求
其他
跨域同源策略做了很嚴(yán)格的限制,但是在實(shí)際的場景中,又確實(shí)有很多地方需要突破同源策略的限制,也就是我們常說的跨域
1. cookie同源策略最早被提出的時(shí)候,為的就是防止不同域名的網(wǎng)頁之間共享 cookie,但是如果兩個(gè)網(wǎng)頁的一級(jí)域名是相同的,可以通過設(shè)置 document.domain來共享 cookie。
舉個(gè)例子,
https://market.douban.com和https://book.douban.com,這兩個(gè)網(wǎng)頁的一級(jí)域名都是 douban.com,如果我在 market.douban.com中執(zhí)行了
document.domain = "douban.com" document.cookie = "cross=yes" 或 document.cookie = "cross=yes;path=/;domain=douban.com"
這樣設(shè)置了 cookie 之后,在 book.douban.com 中是可以取到這個(gè) cookie 的。
除了在前端設(shè)置之外,也可以直接在 response 里將 cookie 的 domain 設(shè)置成 .douban.com。
2. Ajax在使用 ajax 的過程中,我們碰到的同源限制的問題是最多的。
針對(duì) ajax ,我們有三種方式可以繞過同源策略的限制:
2.1 設(shè)置 CORS設(shè)置 cross-domain 是目前在 ajax 中最常用的一種跨域的方式,相比jsonp和websoket也是最安全的一種方式。
唯一美中不足的是低版本的瀏覽器支持的不是很好
IE ? 5.5+ ? 8+2 ? 10+1 ? 112.1.1 CORS 的運(yùn)作Edge ?
Firefox ? 2+ ? 3.5+
Chrome ? 4+1 ? 13+
Safari ? 3.1+ ? 4+1 ? 6+3
Opera ? 9+ ? 12+
1Does not support CORS for images in
2Supported somewhat in IE8 and IE9 using the XDomainRequest object (but has limitations)
3Does not support CORS for in : https://bugs.webkit.org/show_...
CROS 的設(shè)置,大部分是需要在服務(wù)端進(jìn)行設(shè)置,在服務(wù)端設(shè)置之前,先來看一下 CROS 在瀏覽器中是怎么運(yùn)作的:
首先,在瀏覽器中,http 請(qǐng)求將被分為兩種 簡單請(qǐng)求(simple request) 和 非簡單請(qǐng)求(not-so-simple request)。
簡單請(qǐng)求的判斷包括兩個(gè)條件:
請(qǐng)求方法必須是一下幾種:
HEAD
GET
POST
HTTP 頭只能包括以下信息:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type: 只限于[application/x-www-form-urlencoded, multipart/form-data, text/plain]
不能同時(shí)滿足以上兩個(gè)條件的,就都視作非簡單請(qǐng)求
2.1.2 簡單請(qǐng)求(simple request)瀏覽器在處理簡單請(qǐng)求時(shí),會(huì)在 Header 中加上一個(gè) origin(protocal + host + path + port) 字段,來標(biāo)明這個(gè)請(qǐng)求是來自哪里。
在 CROS 請(qǐng)求中,默認(rèn)是不會(huì)攜帶 cookie之類的用戶信息的,但是不攜帶用戶信息的話,是沒辦法判斷用戶身份的,所以,可以在請(qǐng)求時(shí)將withCredentials設(shè)置為 true, 例如:
var xhr = new XMLHttpRequest() xhr.withCredentials = true
設(shè)置了這個(gè)值之后,在服務(wù)端會(huì)將 response 中的 Access-Control-Allow-Credentials 也設(shè)置為 true,這樣瀏覽器才會(huì)相應(yīng) cookie
在服務(wù)端拿到這個(gè)請(qǐng)求之后,會(huì)對(duì) origin 進(jìn)行判斷,如果是在允許范圍內(nèi)的請(qǐng)求,將會(huì)在 respones 返回的 Header 中加上:
Access-Control-Allow-Origin: origin Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: something
下面來說說這幾個(gè)字段都代表什么:
Access-Control-Allow-Origin
看名字大概就能猜出來,這個(gè)就是告訴瀏覽器,服務(wù)端接受那些域名的訪問。值可以是 request 中的 origin,也可以是 *,也可以是originA | originB 這樣的形式,但是目前看來,在瀏覽器中只支持單一值和*兩種方式。具體可以參考這里:access-control-allow-origin-response-header
Access-Control-Allow-Credentials
從名字上來看,這個(gè)字段標(biāo)明了是否擁有用戶相關(guān)的權(quán)限。
在瀏覽器中,具體表現(xiàn)為是否可以發(fā)送 cookie。這個(gè)值可以選擇性返回,如果不返回的話,默認(rèn)就 是不允許發(fā)送 cookie,如果返回,則只能返回 true。
另外,如果這個(gè)值被設(shè)為了true,那么Access-Control-Allow-Origin就不能被設(shè)置為 *,必須要顯示指定為origin的值;并且返回的cookie因?yàn)槭窃诒豢缬蛟L問的域名下,因?yàn)樽袷赝? 源策略,所以在origin網(wǎng)頁中是不能被讀取到的。
Access-Control-Expose-Headers
從字面意義上來看,這個(gè)字段返回的就是其他可被返回的數(shù)據(jù)。
之所以會(huì)有這個(gè)字段,是因?yàn)樵?b>簡單請(qǐng)求中,response返回的頭信息中,瀏覽器只能拿到以下幾個(gè)基本字段:Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma。
如果想要拿到更多的額外信息,只能在Access-Control-Expose-Headers里設(shè)置,例如:
Access-Control-Expose-Headers: "Foo=foo"
這樣的話,在瀏覽中,就可以獲取 Foo 這個(gè)字段所攜帶的信息了
2.1.3 非簡單請(qǐng)求(not-so-simple request)與簡單請(qǐng)求最大的不同在于,非簡單請(qǐng)求實(shí)際上是發(fā)送了兩個(gè)請(qǐng)求。
首先,在正式請(qǐng)求之前,會(huì)先發(fā)送一個(gè)預(yù)請(qǐng)求(preflight-request),這個(gè)請(qǐng)求的作用是盡可能少的攜帶信息,供服務(wù)端判斷是否響應(yīng)該請(qǐng)求。
瀏覽器發(fā)送預(yù)請(qǐng)求,請(qǐng)求的 Request Method 會(huì)設(shè)置為 options。
另外,還會(huì)帶上這幾個(gè)字段:
Origin: 同簡單請(qǐng)求的origin
Access-Control-Request-Method: 請(qǐng)求將要使用的方法
Access-Control-Request-Headers: 瀏覽器會(huì)額外發(fā)送哪些頭信息
服務(wù)端收到預(yù)請(qǐng)求之后會(huì)根據(jù)request中的origin,Access-Control-Request-Method和Access-Control-Request-Headers判斷是否響應(yīng)該請(qǐng)求。
如果判斷響應(yīng)這個(gè)請(qǐng)求,返回的response中將會(huì)攜帶:
Access-Control-Allow-Origin: origin
Access-Control-Allow-Methods: like request
Access-Control-Allow-Headers: like request
如果否定這個(gè)請(qǐng)求,直接返回不帶這三個(gè)字段的response就可以,瀏覽器將會(huì)把這種返回判斷為失敗的返回,觸發(fā)onerror方法
如果預(yù)請(qǐng)求被正確響應(yīng),接下來就會(huì)發(fā)送正式請(qǐng)求,正式請(qǐng)求的request和正常的 ajax 請(qǐng)求基本沒有區(qū)別,只是會(huì)攜帶 origin 字段;response和簡單請(qǐng)求一樣,會(huì)攜帶上Access-Control-*這些字段
2.2 websocketwebsocket 不遵循同源策略。
但是在 websocket 請(qǐng)求頭中會(huì)帶上 origin 這個(gè)字段,服務(wù)端可以通過這個(gè)字段來判斷是否需要響應(yīng),在瀏覽器端并沒有做任何限制。
2.3 jsonpjsonp 其實(shí)算是一種 hack 形式的請(qǐng)求。
jsonp 的本質(zhì)其實(shí)是請(qǐng)求一段 js 代碼,是對(duì)靜態(tài)文件資源的請(qǐng)求,所以并不遵循同源策略。但是因?yàn)槭菍?duì)靜態(tài)文件資源的請(qǐng)求,所以只能支持 GET 請(qǐng)求,對(duì)于其他方法沒有辦法支持。
3. iframe 3.1 iframe 中的同源策略根據(jù)同源策略的規(guī)定,如果兩個(gè)頁面不同源,那么相互之間其實(shí)是隔離的。
在使用 iframe 的頁面中,雖然我們可以通過iframe.contentWindow,window.parent,window.top等方法拿到window對(duì)象,但是根據(jù)同源策略,瀏覽器將對(duì)非同源的頁面之間的window和location對(duì)象添加限制
不同源的兩個(gè)網(wǎng)頁將不能:
操作彼此的 dom
獲取/調(diào)用彼此 window 對(duì)象中的屬性/方法
不同源的兩個(gè)網(wǎng)頁可以:
改變父/子級(jí)的 url
具體的規(guī)則可以參考這里:integration-with-idl
但是在現(xiàn)實(shí)世界中,有很多場景下,其實(shí)是需要兩個(gè)非同源的 iframe 之間進(jìn)行“跨域”操作的。為了實(shí)現(xiàn)這種“跨域”,我們借用了以下幾種方法:
片段標(biāo)識(shí)符(fragment identifier)
使用 window.name
跨文檔通信
3.2 使用片段標(biāo)識(shí)符(fragment identifier)片段標(biāo)識(shí)符指的就是 url 中 # 之后的部分,也就是我們常說的 location.hash。
使用片段標(biāo)識(shí)符依托于以下幾個(gè)關(guān)鍵點(diǎn):
改變 url 里的這個(gè)部分,是不會(huì)觸發(fā)頁面的刷新的
父級(jí)頁面雖然不能操作 iframe 中的 window 和 dom,但是可以改變 iframe 的 url
window 對(duì)象可以監(jiān)聽 hashchange 事件
通過這幾個(gè)關(guān)鍵點(diǎn),可以實(shí)現(xiàn)基于 hashchange 來操作頁面
3.3 使用 window.namewindow.name這個(gè)屬性最厲害的地方在于,window對(duì)象沒有改變的話,這個(gè) window 跳轉(zhuǎn)的網(wǎng)頁,都讀取 window.name 這個(gè)值。
例如,A 網(wǎng)頁設(shè)置了 window.name,然后跳轉(zhuǎn)到了 B 網(wǎng)頁,但是 B 網(wǎng)頁中,仍然可以讀取到 A 設(shè)置的 window.name
通過這個(gè)特性,在 iframe 中,子頁面可以先設(shè)置 window.name;
然后跳轉(zhuǎn)到一個(gè)跟父頁面同級(jí)的地址,這個(gè) window.name 依然存在,因?yàn)橐呀?jīng)調(diào)到了跟父級(jí)頁面同源的地址中,所以父頁面可以獲取到 iframe.contentWindow中屬性,也就是可以讀取到 window.name 了
這種方法最大的優(yōu)點(diǎn)就是window.name可以傳一個(gè)很長的字符串,但是缺點(diǎn)也比較明顯,就是需要在父級(jí)頁面不停的去檢查子頁面的window.name是否被改變
3.4 跨文檔通信API(Cross-document messaging)雖然上面的兩種方法都可以實(shí)現(xiàn)不同源頁面之間的通信,但是總歸是屬于hack的方法,眼看著大家對(duì)非同源頁面的通信都有需求,所以在 HTML5 規(guī)范中,添加了一個(gè)window.postMessage的方法。
通過這個(gè)方法,可以方便的實(shí)現(xiàn)不同源的頁面之間的通信。
看一個(gè)簡單的例子:
// Page Foo iframe.contentWindow.postMessage("Hello from foo", "/path/to/bar") // Page Bar window.parent.addEventListener("message", function (e) { console.log(e.source) // 發(fā)送消息的窗口 console.log(e.origin) // 消息發(fā)向的網(wǎng)址 console.log(e.data) // 消息內(nèi)容 })2.6 canvas
在 canvas 的使用過程中,也會(huì)碰到同源策略的限制。
以下的幾種操作,都會(huì)受到同源策略的限制:
canvas.toDataURL
canvas.toBlob
canvas.getContent("2d").getImageData(x,y,w,h)
例如:
// 這段 JS 運(yùn)行在 a.com 這個(gè)域名下 var canvas = document.createElement("canvas") var ctx = canvas.getContent("2d") var src = "http://b.com/path/to/a/image" var img = new Image() img.onload = function () { canvas.with = img.style.width canvas.height = img.style.height ctx.drawImage(img) // 以下的這這三種操作都會(huì)報(bào)錯(cuò) canvas.toDataURL("image/jpg") canvas.toBlob(function () {}) ctx.getImageData(0, 0, 10, 10) } img.src = src
運(yùn)行時(shí)會(huì)報(bào)錯(cuò)
Uncaught SecurityError: Failed to execute "toDataURL" on "HTMLCanvasElement": Tainted canvases may not be exported.
可以看到是toDataURL的時(shí)候,因?yàn)?a.com和b.com是不同源的兩個(gè)網(wǎng)頁,觸發(fā)了同源策略的限制。換成toBlob或getImageData會(huì)報(bào)同樣的錯(cuò)誤。
我們來探究以下報(bào)這個(gè)錯(cuò)誤的原因:
首先,所有bitmaps類型的對(duì)象,在被canvas或ImageBitmap使用時(shí),都會(huì)先檢查當(dāng)前這對(duì)象,是不是處在origin clean的狀態(tài)。
然后,所有bitmaps類型的對(duì)象,默認(rèn)情況下,這個(gè)origin clean都是true,但是如果這個(gè)bitmaps被跨域調(diào)用,那么,這個(gè)origin clean將會(huì)被設(shè)置成 false。
再然后,在使用toDataURL,toBlob和getImageData時(shí),都會(huì)先檢查origin clean,如果為 false 的話,就會(huì)拋出SecurityError這樣的異常。
那么,這個(gè)origin clean的狀態(tài),是如何設(shè)置的呢?
可以通過crossOrigin來設(shè)置,看代碼:
var canvas = document.createElement("canvas") var ctx = canvas.getContent("2d") var src = "http://b.com/path/to/a/image" var img = new Image() img.onload = function () { canvas.with = img.style.width canvas.height = img.style.height ctx.drawImage(img) canvas.toDataURL("image/jpg") } img.crossOrigin = "*" img.src = src
加上了crossOrigin這個(gè)屬性,然后執(zhí)行,發(fā)現(xiàn)還會(huì)報(bào)個(gè)錯(cuò):
Image from origin "http://b.com" has been blocked from loading by Cross-Origin Resource Sharing policy: No "Access-Control-Allow-Origin" header is present on the requested resource. Origin "http://localhost:3000" is therefore not allowed access
看報(bào)錯(cuò)信息大概可以知道,是Access-Control-Allow-Origin這里出了問題,只需要把Access-Control-Allow-Origin設(shè)置成對(duì)應(yīng)的值就可以了。
更具體的原因可以參考這里:Security with canvas elements
2.7 flashflash在進(jìn)行 HTTP 請(qǐng)求時(shí),也遵循同源策略。
但是相比較以上的各種場景和繞過同源策略的方法,flash 的跨域請(qǐng)求設(shè)置很容易,只需要在目標(biāo)服務(wù)的根目錄下設(shè)置一個(gè)crossdomain.xml文件即可。
這個(gè)文件中會(huì)規(guī)定哪些域可以訪問當(dāng)前服務(wù),看一個(gè)真實(shí)世界里的例子:
參考文章:
Same-origin policy
browsers
瀏覽器同源政策及其規(guī)避方法
security-with-canvas-elements
concept-canvas-origin-clean
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/111683.html
摘要:同源策略在這之前需要先熟悉一下這個(gè)概念,同源指請(qǐng)求協(xié)議相同,主機(jī)名相同,端口相同,涉及安全的策略。同源策略主要限制的是不同源之間的交互操作,對(duì)于跨域內(nèi)嵌的資源不受該策略限制。 問題起因是在使用weibo api的時(shí)候,發(fā)現(xiàn)有一個(gè)報(bào)錯(cuò)。weibo api是https協(xié)議,我本地是模擬的回調(diào)域名,然后進(jìn)行數(shù)據(jù)通信,本地http協(xié)議,于是乎就報(bào)錯(cuò)了。出于對(duì)postMessage的不是很熟悉,...
摘要:存在跨域的情況網(wǎng)絡(luò)協(xié)議不同,如協(xié)議訪問協(xié)議。域名和域名對(duì)應(yīng)如訪問跨域請(qǐng)求資源的方法代理定義和用法代理用于將請(qǐng)求發(fā)送給后臺(tái)服務(wù)器,通過服務(wù)器來發(fā)送請(qǐng)求,然后將請(qǐng)求的結(jié)果傳遞給前端。定義和用法是現(xiàn)代瀏覽器支持跨域資源請(qǐng)求的一種最常用的方式。 1、什么是跨域? 由于瀏覽器同源策略,凡是發(fā)送請(qǐng)求url的協(xié)議、域名、端口三者之間任意一與當(dāng)前頁面地址不同即為跨域。存在跨域的情況: 網(wǎng)絡(luò)協(xié)議不同,...
閱讀 1203·2021-11-23 10:10
閱讀 1548·2021-09-30 09:47
閱讀 931·2021-09-27 14:02
閱讀 3007·2019-08-30 15:45
閱讀 3045·2019-08-30 14:11
閱讀 3639·2019-08-29 14:05
閱讀 1845·2019-08-29 13:51
閱讀 2236·2019-08-29 11:33