同源策略與跨域

同源策略

同源策略same origin policy中的重要內容就是URL(uniform resource locator),統一資源定位符,俗稱網址。URL中的resource資源就是css,js,html,img等內容。
圖片描述javascript

origin

源包括當前頁面的域名、協議、端口號。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

1.JSONP實現跨域

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>

需後臺配合,代碼以下圖
圖片描述

封裝一個JSONP函數

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)

2.CORS 跨域資源共享 Cross-Origin Resource Sharing

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的網站請求數據。

3.HTML5跨文檔通訊API window.postMessage

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()方法還可讀取其餘窗口的LocalStorage
window.open("http://www.bbb.com:8881/b_index.html","title") //主頁面中打開子頁面
window.opener.postMessage(${msg},"http://www.aaa.com:8888") //引用打開子頁面的主頁面

4.降域 實現iframe窗口跨域訪問 實現不一樣子域頁面cookies共享

當兩個網頁一級域名相同,只是二級域名不一樣,瀏覽器容許經過設置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文件中的信息

相關文章
相關標籤/搜索