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