同源策略same origin policy中的重要內容就是URL(uniform resource locator),統一資源定位符,俗稱網址。URL中的resource資源就是css,js,html,img等內容。
javascript
源包括當前頁面的域名、協議、端口號。http協議默認端口是80,https協議默認端口是443。同源策略是瀏覽器的一個安全機制功能,Same Origin Policy,同源就是當協議、域名、端口號一致時就是同源。不一樣源的客戶端腳本在沒有明確受權下,不能讀寫對方的資源。簡單地理解就是由於同源策略的限制,它是瀏覽器爲了安全性考慮一種很是重要的策略,a.com 域名下的js沒法操做b.com或是c.a.com域名下的對象。更詳細的說明能夠看下錶:
css
瀏覽器基於用戶的隱私安全目的,防止惡意網站竊取數據(只是瀏覽器有這個同源策略設置,可是用命令行curl請求某個跨域地址時能獲得相應結果),不容許不一樣域名的網站之間互相調用ajax XHR對象,只是不容許XHR對象,對其餘的圖片、js腳本、css腳本仍是能夠經過標籤跨域調用。因此css/js/img能夠跨域請求(即引用),AJAX不能請求跨域的資源。
curl http://www.abc.com 用這個命令得到了http://www.abc.com指向的頁面,一樣,若是這裏的URL指向的是一個文件或者一幅圖均可以直接下載到本地。若是下載的是HTML文檔,那麼缺省的將不顯示文件頭部,即HTML文檔的header。要所有顯示,請加參數 -i,要只顯示頭部,用參數 -I。任什麼時候候,可使用 -v 命令看curl是怎樣工做的,它向服務器發送的全部命令都會顯示出來。爲了斷點續傳,可使用-r參數來指定傳輸範圍。html
【1】 Cookie、LocalStorage 和 IndexedDB 沒法讀取。Cookie 是服務器寫入瀏覽器的一小段信息,只有同源的網頁才能共享。
【2】 DOM 沒法得到。
【3】 AJAX 請求無效(能夠發送,但瀏覽器會拒絕接受響應)。前端
主域名 abc.com
www.abc.com //www是子域名
bbs.abc.com //bbs是子域名
beijing.bbs.abc.com //beijing.bbs是子域名
haidian.beijing.bbs.abc.com //haidian.beijing.bbs是子域名
主域名是不帶www的域名,例如a.com,主域名前面帶前綴的一般都爲二級域名或多級域名,例如www.a.com實際上是二級域名。java
解決跨域問題的兩個前提特別注意:
第一,若是是協議和端口形成的跨域問題「前端」沒法解決。
第二:在跨域問題上,域僅僅是經過「URL的首部」來識別而不會去嘗試判斷相同的ip地址對應着兩個域或兩個域是否在同一個ip上。好比在host文件中可將兩個不一樣域名綁定到同一個IP地址上造成跨域。
「URL的首部」可在console.log控制檯中用window.location.方法名獲取node
跨域就是用某種方法突破同源策略的限制,實現獲取其餘域中的資源。實現跨域通常有四種方法:jquery
JSONP(json with padding)方式, 經過script標籤請求資源,容許用戶在scr地址中傳遞一個callback參數(callback=abc)即將預先定義好的回調函數名以查詢字符串的形式傳遞給服務端,服務端收到請求後會將要返回的數據用這個callback參數(abc)包裹住再返回,即將請求傳入的參數abc做爲函數名來包裹住要返回的JSON數據,好比abc(JSON),這樣客戶端在收到服務端返回的abc(JSON)文件後默認用JS解析執行。經過JSONP就能夠隨意定製本身的函數來自動處理返回數據了。web
jsonp的原理:雖然瀏覽器默認禁止了跨域訪問,但並不由止在頁面中script標籤引用其餘域下的JS文件,好比線上jquery庫,並能夠自由執行引入的JS文件中的function(包括操做cookie、Dom等等)。根據這一點,能夠方便地經過動態建立script節點的方法來實現徹底跨域的通訊。例如a.com/index.html中能夠引用b.com/main.js、b.com/style.css、b.com/logo.png等資源,此類操做不受同源策略限制。實際操做中若是在a.com下用ajax去請求(讀寫)b.com下的內容會被同源策略阻止,但a.com裏若是引用了b.com/main.js,雖然能夠引用,但當這個引用的js文件(在a.com下引用)去讀寫(ajax)b.com的資源時同樣會提示ajax錯誤。注意讀寫和引用有本質區別的,受同源策略限制ajax不能(POST寫/GET讀)請求跨域內容,但能夠經過script引用的方式獲取目標域上js文件,若是在這個被引用的JS文件內存放數據,這樣就能從目標域獲取到數據了,這就是JSONP實現的原理。
JSONP與JSON沒有關係。JSON是規定語法的一種字符串的寫法。JSONP(json with padding),這裏的padding就是被請求的目標域B域返回的數據其外層包裹的A域預先定義好的函數+括號。JSONP就是動態的script,即A域前臺傳什麼callback=abc給B域名後臺,B域就生成對應的abc方法,這個方法的執行過程是A域預先定義好的
jsonp的缺點:
1.安全問題,src引用是開放的,因此jsonp的資源都被全部人訪問到。解決方法是用jsonp中的token參數,經過A域和B域共用同一套cookie來驗證A的身份。
2.只能用GET方式不能用POST方式獲取數據即只能讀不能寫,由於是基於scr引用的,引用是get請求。
3.可被注入惡意代碼如?callback=alert(1); 這問題只能用正則過濾字符串的方法解決,過濾callback後的內容不能有括號之類的條件ajax
JSONP的實現代碼以下:
1.定義數據處理函數json
appendHtml(){ xxxxx }
2.建立script標籤,src的地址執行後端接口,最後加個參數callback=appendHtml
.如:
var script=document.createElement('script') script.src="http://127.0.0.1/getNews?callback=appendHtml"
3.服務端在收到請求後,解析參數,計算返還數據,輸出 appendHtml(data) 字符串。
4.前臺頁面收到服務端返回的appendHtml(data)字符串所構建的script標籤,頁面加載這個script標籤時作爲js執行。此時會調用appendHtml()函數,將data作爲參數。
注意:JSONP實現的前提是後端必須有JSONP的API接口,即後端有將前端傳入的參數做爲函數名包裹數據返回js文件的邏輯。如:
var data=[{"a":1,"b":2}] var cb=req.query.callback; if(cb){ res.send(`${cb}(JSON.stringify(${data}))`) }else{ res.send(`${data}`) }
<script>//動態建立script標籤 function abc(){ var script=document.createElement('script') script.setAttribute("type","text/javascript") script.src='//b.com/data.js?callback=xxx' document.body.appendChild(script) document.body.removeChild(script)//插入標籤且加載數據實現後再刪除,更簡潔 } abc()//聲明後再調用,動態獲取src引用的資源 </script>
需後臺配合,代碼以下圖
function jsonp(url,_callback){ var scriptNode=document.createElement("script") scriptNode.setAttribute("type","text/javascript") scriptNode.setAttribute("src",`${url}?callback=${_callback}`) document.head.appendChild(scriptNode) } function getMusic(content){ /*定義數據返回時所執行的回調函數*/ console.log(content.song[0]) var comeMusic=content.song[0], musicTitle=comeMusic.title, musicArtist=comeMusic.artist, imgSrc=comeMusic.picture, _src=imgSrc.match(/.[^@]*/g)[0] /*正則匹配返回的圖片地址*/ console.log(_src) imgNode.src=_src h3Node.innerText=musicTitle h6Node.innerText=musicArtist }
調用jsonp函數:jsonp(http://www.aaa.com,getMusic)
CORS容許瀏覽器向跨域服務器發出XMLHttpRequest請求,突破了AJAX只能同源使用的限制。CORS須要瀏覽器和服務器同時支持,目前,全部瀏覽器都支持該功能,IE瀏覽器不能低於IE10。CORS原理:瀏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感受。只要服務器實現了CORS接口,就能夠跨源通訊。
以nodejs server-mock後臺爲例:
res.header("Accept-Control-Allow-Origin","http://www.a.com:8080")//只容許http://www.a.com:8080這個源發起的請求
res.header("Accept-Control-Allow-Origin","*")//容許全部源發起的請求
瀏覽器將跨域請求分爲兩類
簡單請求(simple request),同時知足如下兩個條件
【1】請求方法是HEAD/GET/POST之一
【2】HTTP請求頭信息不超出如下幾種字段
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain
非簡單請求(not-so-simple request),不知足以上兩個條件的請求就是非簡單請求
AJAX發起跨域請求時,若是是簡單類型請求,請求頭信息以下:
GET /cors HTTP/1.1
Origin: http://api.bob.com //發起這次請求的所在源(協議+域名+端口號)
Host: api.alice.com //要訪問的目標域
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
若是Origin指定的源,不在服務端許可範圍內,服務器會返回一個正常的HTTP迴應。但這個響應頭信息沒有包含Access-Control-Allow-Origin字段。瀏覽器接收後就知道出錯了,從而拋出一個錯誤,被XMLHttpRequest的onerror回調函數捕獲。這種錯誤沒法經過狀態碼識別,由於HTTP迴應的狀態碼有多是200。
若是Origin指定的域名在許可範圍內,服務器返回的響應,響應頭會包含如下信息字段
Access-Control-Allow-Origin: http://api.bob.com //容許來自源http://api.bob.com的訪問,若是是*則表明容許來自全部源的訪問
Access-Control-Allow-Credentials: true //是否容許客戶端在請求中發送Cookie
Access-Control-Expose-Headers: FooBar //容許CORS請求拿到除默認的Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma外的其餘字段。這裏容許拿到foobar字段信息。
Content-Type: text/html; charset=utf-8
CORS請求默認不包含Cookie信息,若是須要包含Cookie信息,一方面要服務器贊成
Access-Control-Allow-Credentials: true //但Access-Control-Allow-origin 不能是*
另外一方面,開發者必須在AJAX請求中打開withCredentials屬性。
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
此時Cookie依然遵循同源政策,只有用服務器域名設置的Cookie纔會上傳,其餘域名的Cookie並不會上傳,且(跨源)原網頁代碼中的document.cookie也沒法讀取服務器域名下的Cookie。
AJAX發起跨域請求時,若是是非簡單類型請求,好比請求方法是PUT或DELETE,或者Content-Type字段的類型是application/json,非簡單請求的CORS請求,會在正式通訊以前,增長一次HTTP查詢請求,稱爲「預檢」請求,瀏覽器先詢問服務器,當前網頁所在的域名是否在服務器的許可名單之中,以及可使用哪些HTTP動詞和頭信息字段。只有獲得確定答覆,瀏覽器纔會發出正式的XMLHttpRequest請求,不然就報錯。
好比js代碼以下:
var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true); //方法是PUT
xhr.setRequestHeader('X-Custom-Header', 'value'); //自定義請求頭信息
xhr.send();
請求頭信息以下:
OPTIONS /cors HTTP/1.1 //方法是OPTIONS,表示這個請求是用來詢問的。
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
若是服務器否認了「預檢」請求,會返回一個正常的HTTP迴應,可是沒有任何CORS相關的頭信息字段。瀏覽器接收後就知道出錯了,被XMLHttpRequest對象的onerror回調函數捕獲。
若是服務器接受了「預檢」請求,之後每次瀏覽器正常的CORS請求,就都跟簡單請求同樣,會有一個Origin頭信息字段。服務器的迴應,也都會有一個Access-Control-Allow-Origin頭信息字段。服務器發送響應頭信息以下:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT //代表服務器支持的全部跨域請求的方法
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
JSONP只支持GET請求,CORS支持全部類型的HTTP請求。JSONP的優點在於支持老式瀏覽器,以及能夠向不支持CORS的網站請求數據。
HTML5中最酷的新功能之一就是 跨文檔消息傳輸Cross Document Messaging。下一代瀏覽器都將支持這個功能:Chrome 2.0+、Internet Explorer 8.0+, Firefox 3.0+, Opera 9.6+, 和 Safari 4.0+ 。 Facebook已經使用了這個功能,用postMessage支持基於web的實時消息傳遞。
舉例,父窗口aaa.com向子窗口bbb.com發消息,調用postMessage方法以下:
var popup = window.open('http://bbb.com', 'title'); popup.postMessage('Hello World!', 'http://bbb.com');
postMessage方法的第一個參數是具體的信息內容(支持任意類型),第二個參數是接收消息目標窗口的源origin(協議 + 域名 + 端口)也能夠設爲*,表示不限制域名,向全部窗口發送。
子窗口向父窗口發送消息的寫法以下:window.opener.postMessage('Nice to see you', 'http://aaa.com');
父窗口和子窗口均可以經過message事件,監聽對方的消息:
window.addEventListener('message', function(event) { console.log(e.data); },false);
事件對象event的三個屬性:event.source
:發送消息的窗口對象,對發送消息的窗口對象的引用;event.origin
: 發送消息的窗口的源(協議、域名、端口號)這裏不是接受消息的窗口的源event.data
: 消息內容event.origin
屬性能夠過濾不是發給本窗口的消息,舉例以下:
當bbb網站收到來自aaa網站發來的消息
window.addEventListener('message', receiveMessage); function receiveMessage(event) { if (event.origin !== 'http://aaa.com') return;//判斷髮消息的窗口的源是不是aaa網站的源,這裏的event.origin和postMessage()方法中的origin不同!! if (event.data === 'Hello World') { event.source.postMessage('Hello', event.origin);//這裏event.origin指向aaa網站的源即消息接收的窗口的源 } else { console.log(event.data); } }
otherWindow.postMessage方法中,otherWindow是其餘窗口的一個引用,好比iframe的contentWindow屬性、執行window.open返回的窗口對象、或者是命名過或數值索引的window.frames。即只能適用於open方法打開的頁面相互發送消息,或者經過iframe嵌套的頁面之間發送消息。經過window.postMessage()方法還可讀取其餘窗口的LocalStoragewindow.open("http://www.bbb.com:8881/b_index.html","title") //主頁面中打開子頁面
window.opener.postMessage(
${msg},"http://www.aaa.com:8888") //引用打開子頁面的主頁面
當兩個網頁一級域名相同,只是二級域名不一樣,瀏覽器容許經過設置document.domain來實現iframe窗口相互訪問或設置cookie。降域方式只適用於同一站點下不一樣子域名共享cookie或者iframe頁面中嵌套的子域名頁面之間的訪問。降域不適用訪問LocalStorage 和 IndexedDB,postMessage()方法可訪問LocalStorage
用改寫document.domain+iframe的方法來獲取目標域數據。缺點是安全性差,一個頁面被攻擊後另外一個頁面的數據也會被泄露且不支持ajax方式請求數據。降域只能解決主域相同而二級域名(子域名)不一樣的兩個頁面請求數據的狀況,若是把script.a.com的domian設爲alibaba.com那顯然是會報錯。domain只能設置爲主域名,不能夠在b.a.com中將domain設置爲c.a.com;且只能由子域名改到父域名或父父域名,不能從父父域名改到子域名(好比將b.com改爲script.b.com是不行的)
舉例來講,A網頁是http://w1.example.com/a.html,B網頁是http://w2.example.com/b.html,子域名不一樣默認狀況下會被同源策略阻止訪問。只要將兩個頁面都設置相同的document.domain
,兩個網頁就能夠共享Cookie或在iframe窗口下相互訪問。document.domain = 'example.com'
;
服務器也能夠在設置 Cookie 的時候,指定 Cookie 的所屬域名爲一級域名之後二級域名和三級域名不用作任何設置,均可以讀取這個Cookie。好比.example.com。Set-Cookie: key=value; domain=.example.com; path=/
降域的特色:只能對主域相同子域不一樣的iframe頁面和父頁面(或者open方法打開的主域相同的頁面)形式的跨域起做用,可實現的兩個頁面共享cookie。修改domain時top頁面和iframe頁面都要使用document.domain
去修改爲一致的域名。具體的作法是能夠在http://www.a.com/a.html和http://script.a.com/b.html兩個文件中分別加上document.domain = 'a.com'
;而後經過a.html文件中建立一個iframe,去控制iframe的contentDocument,這樣兩個js文件之間就能夠「交互」了。驗證步驟以下:
1.本地文件夾中有兩個文件index.html(www.a.com下的網頁),b.html(script.a.com下的網頁),a.com下的index.html頁面中有iframe頁面(src="http://script.a.com:8080/b.html")
2.因爲域僅僅是經過「URL的首部」來識別,不會判斷兩個不一樣域名是否爲同一IP地址。根據這點,修改本地host文件增長兩行,子域名不一樣,主域名相同來模擬跨域。127.0.0.1 www.a.com
127.0.0.1 script.a.com
3.用mock start命令啓動服務器並分別訪問
http://www.a.com:8080/index.html
http://script.a.com:8080/b.html
兩個頁面console.log(document.domain)
時分別返回www.a.com和script.a.com,此時是跨域狀態。
4.當在www.a.com下的html頁面中執行如下腳本時提示var ccc=document.getElementsByTagName("iframe")[0].contentDocument
(注:.contentDocument方法可操做iframe頁面內的信息)
5.解決方案:www.a.com下的index.html頁面和script.a.com下的b.html頁面中都增長腳本,將兩個頁面的域設置成相同的主域名
<script> document.domain="a.com" </script>
再次執行var ccc=document.getElementsByTagName("iframe")[0].contentDocument
返回的就是b.html文件中的信息