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