詳解瀏覽器跨域

1、什麼是跨域?

JavaScript出於安全方面的考慮作的同源策略的限制,不容許跨域訪問其餘資源。一般跨域請求成功後,瀏覽器會拒絕響應服務器端返回的結果。javascript

1.出於哪些方面的安全考慮?

同源政策的目的是爲了防止惡意網站竊取用戶數據信息冒充用戶作一些操做。同源限制只是提升攻擊成本。若是沒有JavaScript同源限制:html

(1)CSRF攻擊

(2)XSS攻擊

2.什麼是同源?

域名、協議、端口均相同。舉例來講,http://www.example.com/dir/page.html這個網址,協議是http://,域名是www.example.com,端口是80(默認端口能夠省略)java

3.作了哪些限制?

(1)Window對象之間的跨源通訊:沒法讀取Cookie(cookie是隻會區分域名,不會區分端口的,因此在處理cookie 的時候必須首先你本身爲cookie加上端口的標示以便區分。這裏要感謝評論區指正的道友)、LocalStorage 、IndexDB 和獲取DOM,但經過如下標籤能夠跨域訪問資源:

<img src="URL">
<link href="URL">
<script src="URL">
<iframe src="URL">
<form action="URL" method="get/post">
  First name: <input type="text" name="fname"><br>
  Last name: <input type="text" name="lname"><br>
  <input type="submit" value="提交">
</form>

另外,若是是非同源的網頁,目前容許經過 JavaScript 腳本能夠拿到其餘窗口/網頁的window對象的九個屬性和四個方法。web

  • window.closed
  • window.frames
  • window.length
  • window.location
  • window.opener
  • window.parent
  • window.self
  • window.top
  • window.window
  • window.blur()
  • window.close()
  • window.focus()
  • window.postMessage()

其中,只有window.location是可讀寫(非同源的狀況下,也只容許調用location.replace方法和寫入location.href屬性)的,其餘八個所有都是隻讀。json

(3)客戶端服務器間:瀏覽器拒絕接受AJAX 請求的響應。

4.怎麼樣算是跨域?

以下相對http://store.company.com/dir/page.html同源檢測的示例:api

URL 結果 緣由
http://store.company.com/dir2/other.html 成功  只有路徑不一樣
http://store.company.com/dir/inner/another.html 成功  只有路徑不一樣
https://store.company.com/secure.html 失敗 不一樣協議 ( https和http )
http://store.company.com:81/dir/etc.html 失敗 不一樣端口 ( http:// 80是默認的)
http://news.company.com/dir/other.html 失敗 不一樣域名 ( news和store )

注意:域名與其對應的ip也不能成功訪問跨域

2、如何解決跨域限制?

1.Window對象之間的跨源通訊

(1)若是兩個網頁只是二級域名不一樣,一級域名相同

① 瀏覽器容許經過設置document.domain共享 Cookie,拿到 DOM。

/****A網頁:http://t1.example.com/a.html*/

document.domain = 'example.com';
//設置cookie
document.cookie = "test1=hello";


/****B網頁:http://t2.example.com/b.html,設置相同的document.domain*/

document.domain = 'example.com';
//訪問A網頁的cookie
console.log(document.cookie);

/*注意:A 和 B 兩個網頁都須要設置document.domain屬性,才能達到同源的目的。由於設置document.domain的同時,會把端口重置爲null,所以若是隻設置一個網頁的document.domain,會致使兩個網址的端口不一樣,仍是達不到同源的目的*/

② 另外,服務器也能夠在設置Cookie的時候,指定Cookie的所屬域名爲一級域名,二/三級域名不用作任何設置,就能夠讀取這個Cookie

Set-Cookie: key=value; domain=.example.com; path=/

缺點:這種方法只適用於共享 Cookie 和 iframe 窗口,LocalStorage 和 IndexDB 沒法經過這種方法跨域共享瀏覽器

(2)對於徹底不一樣源的網站,能夠經過如下三種解決跨域窗口的通訊問題:

①片斷識別符(URL的#號後面的部分)

只是改變url的片斷標識符,頁面不會從新刷新,父窗口能夠把信息,寫入子窗口的片斷標識符。緩存

//****父窗口把要共享的信息添加到子窗口url的#後

var src = originURL + '#' + data;
document.getElementById('myIFrame').src = src;



//****子窗口監聽窗口變化

window.onhashchange = ()=>{
//獲取url的#後的數據
  var data= window.location.hash;
}
//子窗口也能夠經過這種方式向父窗口共享信息
parent.location.href= target + "#" + hash;

②window.name

瀏覽器窗口的window.name屬性,只要在同一個窗口裏不管是否同源,前一個網頁設置了這個屬性,後一個網頁能夠讀取它,且容量很大,能夠放置很是長的字符串。安全

//****子窗口:http://child.url.com/xxx.html,將信息寫入window.name屬性:

window.name = data;

location = 'http://parent.url.com/other.html';//接着,子窗口跳回一個與父窗口同域的網址。



//****父窗口:http://parent.url.com/xxx.html,先打開不一樣源的子窗口網頁:

var iframe = document.createElement('iframe');

iframe.id='myFrame';

iframe.src = 'http://child.url.com/xxx.html';//iframe能夠跨域加載資源

document.body.appendChild(iframe);

//而後,父窗口就能夠讀取子窗口的window.name了。

var data = document.getElementById('myFrame').contentWindow.name;

缺點:必須監聽子窗口window.name屬性的變化,影響網頁性能。

③window.postMessage

HTML5爲了解決Window對象之間的跨源通訊問題(例如:在頁面和它的彈出窗口之間,或嵌入其中的iframe之間,具體參見:https://www.w3cschool.cn/fetch_api/fetch_api-lx142x8t.html),引入了——跨文檔通訊 API。這個API爲window對象新增了一個window.postMessage方法,容許跨窗口通訊,不論這兩個窗口是否同源。

/*語法:otherWindow.postMessage(message, targetOrigin, [transfer]);
message:要發送的數據信息
targetOrigin:接收消息的窗口的源(origin),即"協議 + 域名 + 端口"。也能夠設爲*,表示不限制域名,向全部窗口發送。
*/


//****父窗口"http://aaa.com"向子窗口"http://bbb.com"發消息

var popup = window.open('http://bbb.com', 'title');
popup.postMessage('Hello World!', 'http://bbb.com');


//****子窗口經過message事件,監聽發送者的消息
window.addEventListener('message', function(event) {
  console.log(event.source);//發送源自的窗口:popup (子窗口能夠經過event.source屬性引用父窗口,而後發送消息)
  console.log(event.origin);//發送源自的域:"http://aaa.com"(經過event.origin驗證發送者,分派事件的origin屬性的值不受調用窗口中document.domain的當前值的影響)
  console.log(event.data);//消息內容:'Hello World!'
},false);

安全問題

①若是您不但願從其餘網站接收message,請不要爲message事件添加任何事件偵聽器這是一個徹底萬無一失的方式來避免安全問題 

②若是您確實但願從其餘網站接收message,請始終使用origin和source屬性驗證發件人的身份,以避免收到惡意網站發送的惡意信息。 

③當您使用postMessage將數據發送到其餘窗口時,始終指定精確的目標origin,而不是*,以避免被惡意網站中間攔截postMessage發送的信息

詳情查看:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage

2.AJAX

同源政策規定,AJAX請求只能發給同源的網址,不然就報錯。三種解決方案:

(1)架設服務器代理

瀏覽器請求同源服務器,再由後者請求外部服務

(2)JSONP

JSONP是服務器與客戶端跨源通訊的經常使用方法,簡單適用,兼容性好。

基本原理:

①網頁添加一個<script>元素,向服務器請求一個腳本,直接做爲代碼運行,這不受同源政策限制,能夠跨域請求。

②服務器收到請求後,拼接一個字符串,將 JSON 數據放在函數名裏面,做爲字符串返回(bar({...})

③客戶端會將服務器返回的字符串,做爲代碼解析,由於瀏覽器認爲,這是<script>標籤請求的腳本內容。這時,客戶端只要定義了bar()函數,就能在該函數體內,拿到服務器返回的 JSON 數據。

//請求的腳本網址有一個callback參數(?callback=bar),用來告訴服務器,客戶端的回調函數名稱(bar)
<script src="http://api.foo.com?callback=bar"></script>


//定義bar()函數,在該函數體內,拿到服務器返回的 JSON 數據
function foo(data) {
  console.log('服務器返回:' + data.id);
};

//服務器收到這個請求之後,會將數據放在回調函數的參數位置返回。
foo({
  'ip': '8.8.8.8'
});

缺點:只能get請求

(3)WebSocket

WebSocket協議是一種基於TCP的網絡協議,取代用HTTP做爲傳輸層的雙向通信技術——容許服務器主動發送信息給客戶端。使用ws://(非加密)和wss://(加密)做爲協議前綴。

①服務器根據WebSocket請求頭的Origin字段(表示:請求源自哪一個域名),判斷是否許可本次通訊

②若是該域名在白名單內,服務器就會作出迴應,因此沒有同源限制

//websocket請求頭(摘自網絡)

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

//判斷爲白名單後,服務端作出迴應

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

(3)CORS

CORS是跨源資源分享(Cross-Origin Resource Sharing)的縮寫,是W3C標準,是跨源AJAX請求的根本解決方法。

  • 相比JSONP只能發GET請求,CORS容許任何類型的請求。
  • CORS 須要瀏覽器和服務器同時支持,實現 CORS 通訊的關鍵是服務器。只要服務器實現了 CORS 接口,就能夠跨域通訊。
  • CORS 請求分紅兩類,劃分的緣由是,表單在歷史上一直能夠跨域發出請求。簡單請求就是表單請求,瀏覽器沿襲了傳統的處理方式,不把行爲複雜化,不然開發者可能轉而使用表單,規避 CORS 的限制。對於非簡單請求,瀏覽器會採用新的處理方式:

①簡單請求:簡單的 HTTP 方法(head,get,post)與簡單的 HTTP 頭信息(Accept,Accept-Language,Content-Language,Last-Event-ID,Content-Type:只限於三個值application/x-www-form-urlencodedmultipart/form-datatext/plain)的結合。

  • 瀏覽器發現跨域 AJAX 請求是簡單請求,直接發出 CORS 請求,自動在頭信息之中,添加一個Origin字段。
  • 若是Origin指定的源,不在許可範圍內,服務器會返回一個正常的 HTTP 迴應,頭信息Access-Control-Allow-Origin沒有包含Origin。瀏覽器收到後從而拋出一個錯誤,被XMLHttpRequest的onerror回調函數捕獲。注意,這種錯誤沒法經過狀態碼識別,由於 HTTP 迴應的狀態碼有多是200。
  • 若是Origin指定的域名在許可範圍內,服務端響應頭之中Access-Control-Allow-Origin字段包含請求頭中Origin字段的值。

//****JavaScript腳本:
var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.withCredentials = true;/*在 AJAX 請求中打開withCredentials屬性,從而向服務器發送 Cookies。可是,若是服務器端的響應中未攜帶 Access-Control-Allow-Credentials: true ,瀏覽器將不會把響應內容返回給請求的發送者。*/
xhr.send();



//***請求頭:
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指定的域名在許可範圍內,服務端響應頭之中,會多出幾個頭信息字段,有三個與 CORS 請求相關的字段,都以Access-Control-開頭。
Access-Control-Allow-Origin: http://api.bob.com  /*必須字段。值能夠是請求頭Origin字段的值(若是服務端指定了具體的Origin域名,則響應頭中的 Vary 字段的值必須包含 「Origin」,告訴客戶端:服務器對不一樣的源站返回不一樣的內容),對於不須要攜帶身份憑證的請求(請求頭沒有攜帶cookie信息),服務器能夠指定該字段的值爲一個*,表示接受任意域名的請求。*/
Access-Control-Allow-Credentials: true /*可選字段。表示服務器明確許可瀏覽器能夠把 Cookie 包含在請求中,一塊兒發給服務器。但,須 AJAX 請求中打開withCredentials屬性才起做用*/
Access-Control-Expose-Headers: FooBar /*在跨域訪問時,XMLHttpRequest對象的getResponseHeader()方法只能拿到一些最基本的響應頭,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,若是要訪問其餘頭,則須要服務器設置本響應頭,把容許瀏覽器訪問的頭放入白名單*/
Content-Type: text/html; charset=utf-8

②非簡單請求:非簡單請求是那種對服務器提出特殊要求的請求,好比:請求方法是PUTDELETE,或者Content-Type字段的類型是application/json

  • 非簡單請求的 CORS 請求,會在正式通訊以前,增長一次 HTTP 查詢請求,稱爲「預檢」請求(preflight)。"預檢請求「的使用,能夠避免跨域請求對服務器的用戶數據產生未預期的影響。
  • 服務器收到「預檢」請求之後,檢查了OriginAccess-Control-Request-MethodAccess-Control-Request-Headers字段之後,確認容許跨源請求,就能夠作出迴應。
  • 若是服務器否認了「預檢」請求,會返回一個正常的 HTTP 迴應,可是沒有任何 CORS 相關的頭信息字段,或者明確表示請求不符合條件。
  • 一旦服務器經過了「預檢」請求,之後每次瀏覽器正常的 CORS 請求,就都跟簡單請求同樣,會有一個Origin頭信息字段。服務器的迴應,也都會有一個Access-Control-Allow-Origin頭信息字段。

//****JavaScript腳本:
var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();



//****「預檢」請求頭
OPTIONS /cors HTTP/1.1  /*「預檢」請求用的請求方法是OPTIONS,表示這個請求是用來詢問的*/
Origin: http://api.bob.com  /*表示請求來自哪一個源*/
Access-Control-Request-Method: PUT  /*該字段是必須的,用來列出瀏覽器的 CORS 請求會用到哪些 HTTP 方法,本例是PUT*/
Access-Control-Request-Headers: X-Custom-Header  /*該字段是一個逗號分隔的字符串,指定瀏覽器 CORS 請求會額外發送的頭信息字段,本例是X-Custom-Header*/
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...




①//****服務器否認了「預檢」請求的響應頭:
OPTIONS http://api.bob.com HTTP/1.1
Status: 200
Access-Control-Allow-Origin: https://notyourdomain.com/*明確不包括髮出請求的http://api.bob.com*/
Access-Control-Allow-Method: POST


//****瀏覽器發現服務器不一樣意預檢請求,觸發一個錯誤,被XMLHttpRequest對象的onerror回調函數捕獲。控制檯報錯信息:
XMLHttpRequest cannot load http://api.alice.com.
Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.


②//****服務器容許了「預檢」請求的迴應頭:
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com  /*表示http://api.bob.com能夠請求數據。該字段也能夠設爲星號,表示贊成任意跨源請求。*/
Access-Control-Allow-Methods: GET, POST, PUT  /*該字段必需,它的值是逗號分隔的一個字符串,代表服務器支持的全部跨域請求的方法。注意,返回的是全部支持的方法,而不單是瀏覽器請求的那個方法。這是爲了不屢次「預檢」請求。*/
Access-Control-Allow-Headers: X-Custom-Header  /*若是瀏覽器請求包括Access-Control-Request-Headers字段,則Access-Control-Allow-Headers字段是必需的。它也是一個逗號分隔的字符串,代表服務器支持的全部頭信息字段,不限於瀏覽器在「預檢」中請求的字段。*/
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
Access-Control-Max-Age: 1728000  /*該字段可選,用來指定本次預檢請求的有效期,單位爲秒。這裏有效期是20天(1728000秒),即容許緩存該條迴應1728000秒(即20天),在此期間,不用發出另外一條預檢請求。*/


//****「預檢」請求經過以後,瀏覽器的會再發一個正常 CORS 請求:
PUT /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...



//****而後,服務器正常的迴應:
Access-Control-Allow-Origin: http://api.bob.com
Content-Type: text/html; charset=utf-8

缺點:兼容性很差(>ie10)

 

 

 

 

具體參見:https://wangdoc.com/javascript/bom/cors.html

詳情查看:https://wangdoc.com/javascript/bom/same-origin.html

其餘相關連接:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessagehttps://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

相關文章
相關標籤/搜索