<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # 同源限制 瀏覽器安全的基石是“同源政策”([same-origin policy](https://en.wikipedia.org/wiki/Same-origin_policy))。很多開發者都知道這一點,但了解得不全面。 ## 概述 ### 含義 1995年,同源政策由 Netscape 公司引入瀏覽器。目前,所有瀏覽器都實行這個政策。 最初,它的含義是指,A 網頁設置的 Cookie,B 網頁不能打開,除非這兩個網頁“同源”。所謂“同源”指的是“三個相同”。 > - 協議相同 > - 域名相同 > - 端口相同(這點可以忽略,詳見下文) 舉例來說,`http://www.example.com/dir/page.html`這個網址,協議是`http://`,域名是`www.example.com`,端口是`80`(默認端口可以省略),它的同源情況如下。 - `http://www.example.com/dir2/other.html`:同源 - `http://example.com/dir/other.html`:不同源(域名不同) - `http://v2.www.example.com/dir/other.html`:不同源(域名不同) - `http://www.example.com:81/dir/other.html`:不同源(端口不同) - `https://www.example.com/dir/page.html`:不同源(協議不同) 注意,標準規定端口不同的網址不是同源(比如8000端口和8001端口不是同源),但是瀏覽器沒有遵守這條規定。實際上,同一個網域的不同端口,是可以互相讀取 Cookie 的。 ### 目的 同源政策的目的,是為了保證用戶信息的安全,防止惡意的網站竊取數據。 設想這樣一種情況:A 網站是一家銀行,用戶登錄以后,A 網站在用戶的機器上設置了一個 Cookie,包含了一些隱私信息。用戶離開 A 網站以后,又去訪問 B 網站,如果沒有同源限制,B 網站可以讀取 A 網站的 Cookie,那么隱私就泄漏了。更可怕的是,Cookie 往往用來保存用戶的登錄狀態,如果用戶沒有退出登錄,其他網站就可以冒充用戶,為所欲為。因為瀏覽器同時還規定,提交表單不受同源政策的限制。 由此可見,同源政策是必需的,否則 Cookie 可以共享,互聯網就毫無安全可言了。 ### 限制范圍 隨著互聯網的發展,同源政策越來越嚴格。目前,如果非同源,共有三種行為受到限制。 > (1) 無法讀取非同源網頁的 Cookie、LocalStorage 和 IndexedDB。 > > (2) 無法接觸非同源網頁的 DOM。 > > (3) 無法向非同源地址發送 AJAX 請求(可以發送,但瀏覽器會拒絕接受響應)。 另外,通過 JavaScript 腳本可以拿到其他窗口的`window`對象。如果是非同源的網頁,目前允許一個窗口可以接觸其他網頁的`window`對象的九個屬性和四個方法。 - 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`對象,非同源的情況下,也只允許調用`location.replace()`方法和寫入`location.href`屬性。 雖然這些限制是必要的,但是有時很不方便,合理的用途也受到影響。下面介紹如何規避上面的限制。 ## Cookie Cookie 是服務器寫入瀏覽器的一小段信息,只有同源的網頁才能共享。如果兩個網頁一級域名相同,只是次級域名不同,瀏覽器允許通過設置`document.domain`共享 Cookie。 舉例來說,A 網頁的網址是`http://w1.example.com/a.html`,B 網頁的網址是`http://w2.example.com/b.html`,那么只要設置相同的`document.domain`,兩個網頁就可以共享 Cookie。因為瀏覽器通過`document.domain`屬性來檢查是否同源。 ```javascript // 兩個網頁都需要設置 document.domain = 'example.com'; ``` 注意,A 和 B 兩個網頁都需要設置`document.domain`屬性,才能達到同源的目的。因為設置`document.domain`的同時,會把端口重置為`null`,因此如果只設置一個網頁的`document.domain`,會導致兩個網址的端口不同,還是達不到同源的目的。 現在,A 網頁通過腳本設置一個 Cookie。 ```javascript document.cookie = "test1=hello"; ``` B 網頁就可以讀到這個 Cookie。 ```javascript var allCookie = document.cookie; ``` 注意,這種方法只適用于 Cookie 和 iframe 窗口,LocalStorage 和 IndexedDB 無法通過這種方法,規避同源政策,而要使用下文介紹 PostMessage API。 另外,服務器也可以在設置 Cookie 的時候,指定 Cookie 的所屬域名為一級域名,比如`.example.com`。 ```http Set-Cookie: key=value; domain=.example.com; path=/ ``` 這樣的話,二級域名和三級域名不用做任何設置,都可以讀取這個 Cookie。 ## iframe 和多窗口通信 `iframe`元素可以在當前網頁之中,嵌入其他網頁。每個`iframe`元素形成自己的窗口,即有自己的`window`對象。`iframe`窗口之中的腳本,可以獲得父窗口和子窗口。但是,只有在同源的情況下,父窗口和子窗口才能通信;如果跨域,就無法拿到對方的 DOM。 比如,父窗口運行下面的命令,如果`iframe`窗口不是同源,就會報錯。 ```javascript document .getElementById("myIFrame") .contentWindow .document // Uncaught DOMException: Blocked a frame from accessing a cross-origin frame. ``` 上面命令中,父窗口想獲取子窗口的 DOM,因為跨域導致報錯。 反之亦然,子窗口獲取主窗口的 DOM 也會報錯。 ```javascript window.parent.document.body // 報錯 ``` 這種情況不僅適用于`iframe`窗口,還適用于`window.open`方法打開的窗口,只要跨域,父窗口與子窗口之間就無法通信。 如果兩個窗口一級域名相同,只是二級域名不同,那么設置上一節介紹的`document.domain`屬性,就可以規避同源政策,拿到 DOM。 對于完全不同源的網站,目前有兩種方法,可以解決跨域窗口的通信問題。 > - 片段識別符(fragment identifier) > - 跨文檔通信API(Cross-document messaging) ### 片段識別符 片段標識符(fragment identifier)指的是,URL 的`#`號后面的部分,比如`http://example.com/x.html#fragment`的`#fragment`。如果只是改變片段標識符,頁面不會重新刷新。 父窗口可以把信息,寫入子窗口的片段標識符。 ```javascript var src = originURL + '#' + data; document.getElementById('myIFrame').src = src; ``` 上面代碼中,父窗口把所要傳遞的信息,寫入 iframe 窗口的片段標識符。 子窗口通過監聽`hashchange`事件得到通知。 ```javascript window.onhashchange = checkMessage; function checkMessage() { var message = window.location.hash; // ... } ``` 同樣的,子窗口也可以改變父窗口的片段標識符。 ```javascript parent.location.href = target + '#' + hash; ``` ### window.postMessage() 上面的這種方法屬于破解,HTML5 為了解決這個問題,引入了一個全新的API:跨文檔通信 API(Cross-document messaging)。 這個 API 為`window`對象新增了一個`window.postMessage`方法,允許跨窗口通信,不論這兩個窗口是否同源。舉例來說,父窗口`aaa.com`向子窗口`bbb.com`發消息,調用`postMessage`方法就可以了。 ```javascript // 父窗口打開一個子窗口 var popup = window.open('http://bbb.com', 'title'); // 父窗口向子窗口發消息 popup.postMessage('Hello World!', 'http://bbb.com'); ``` `postMessage`方法的第一個參數是具體的信息內容,第二個參數是接收消息的窗口的源(origin),即“協議 + 域名 + 端口”。也可以設為`*`,表示不限制域名,向所有窗口發送。 子窗口向父窗口發送消息的寫法類似。 ```javascript // 子窗口向父窗口發消息 window.opener.postMessage('Nice to see you', 'http://aaa.com'); ``` 父窗口和子窗口都可以通過`message`事件,監聽對方的消息。 ```javascript // 父窗口和子窗口都可以用下面的代碼, // 監聽 message 消息 window.addEventListener('message', function (e) { console.log(e.data); },false); ``` `message`事件的參數是事件對象`event`,提供以下三個屬性。 > - `event.source`:發送消息的窗口 > - `event.origin`: 消息發向的網址 > - `event.data`: 消息內容 下面的例子是,子窗口通過`event.source`屬性引用父窗口,然后發送消息。 ```javascript window.addEventListener('message', receiveMessage); function receiveMessage(event) { event.source.postMessage('Nice to see you!', '*'); } ``` 上面代碼有幾個地方需要注意。首先,`receiveMessage`函數里面沒有過濾信息的來源,任意網址發來的信息都會被處理。其次,`postMessage`方法中指定的目標窗口的網址是一個星號,表示該信息可以向任意網址發送。通常來說,這兩種做法是不推薦的,因為不夠安全,可能會被惡意利用。 `event.origin`屬性可以過濾不是發給本窗口的消息。 ```javascript window.addEventListener('message', receiveMessage); function receiveMessage(event) { if (event.origin !== 'http://aaa.com') return; if (event.data === 'Hello World') { event.source.postMessage('Hello', event.origin); } else { console.log(event.data); } } ``` ### LocalStorage 通過`window.postMessage`,讀寫其他窗口的 LocalStorage 也成為了可能。 下面是一個例子,主窗口寫入 iframe 子窗口的`localStorage`。 ```javascript window.onmessage = function(e) { if (e.origin !== 'http://bbb.com') { return; } var payload = JSON.parse(e.data); localStorage.setItem(payload.key, JSON.stringify(payload.data)); }; ``` 上面代碼中,子窗口將父窗口發來的消息,寫入自己的 LocalStorage。 父窗口發送消息的代碼如下。 ```javascript var win = document.getElementsByTagName('iframe')[0].contentWindow; var obj = { name: 'Jack' }; win.postMessage( JSON.stringify({key: 'storage', data: obj}), 'http://bbb.com' ); ``` 加強版的子窗口接收消息的代碼如下。 ```javascript window.onmessage = function(e) { if (e.origin !== 'http://bbb.com') return; var payload = JSON.parse(e.data); switch (payload.method) { case 'set': localStorage.setItem(payload.key, JSON.stringify(payload.data)); break; case 'get': var parent = window.parent; var data = localStorage.getItem(payload.key); parent.postMessage(data, 'http://aaa.com'); break; case 'remove': localStorage.removeItem(payload.key); break; } }; ``` 加強版的父窗口發送消息代碼如下。 ```javascript var win = document.getElementsByTagName('iframe')[0].contentWindow; var obj = { name: 'Jack' }; // 存入對象 win.postMessage( JSON.stringify({key: 'storage', method: 'set', data: obj}), 'http://bbb.com' ); // 讀取對象 win.postMessage( JSON.stringify({key: 'storage', method: "get"}), "*" ); window.onmessage = function(e) { if (e.origin != 'http://aaa.com') return; console.log(JSON.parse(e.data).name); }; ``` ## AJAX 同源政策規定,AJAX 請求只能發給同源的網址,否則就報錯。 除了架設服務器代理(瀏覽器請求同源服務器,再由后者請求外部服務),有三種方法規避這個限制。 > - JSONP > - WebSocket > - CORS ### JSONP JSONP 是服務器與客戶端跨源通信的常用方法。最大特點就是簡單易用,沒有兼容性問題,老式瀏覽器全部支持,服務端改造非常小。 它的做法如下。 第一步,網頁添加一個`<script>`元素,向服務器請求一個腳本,這不受同源政策限制,可以跨域請求。 ```html <script src="http://api.foo.com?callback=bar"></script> ``` 注意,請求的腳本網址有一個`callback`參數(`?callback=bar`),用來告訴服務器,客戶端的回調函數名稱(`bar`)。 第二步,服務器收到請求后,拼接一個字符串,將 JSON 數據放在函數名里面,作為字符串返回(`bar({...})`)。 第三步,客戶端會將服務器返回的字符串,作為代碼解析,因為瀏覽器認為,這是`<script>`標簽請求的腳本內容。這時,客戶端只要定義了`bar()`函數,就能在該函數體內,拿到服務器返回的 JSON 數據。 下面看一個實例。首先,網頁動態插入`<script>`元素,由它向跨域網址發出請求。 ```javascript function addScriptTag(src) { var script = document.createElement('script'); script.setAttribute('type', 'text/javascript'); script.src = src; document.body.appendChild(script); } window.onload = function () { addScriptTag('http://example.com/ip?callback=foo'); } function foo(data) { console.log('Your public IP address is: ' + data.ip); }; ``` 上面代碼通過動態添加`<script>`元素,向服務器`example.com`發出請求。注意,該請求的查詢字符串有一個`callback`參數,用來指定回調函數的名字,這對于 JSONP 是必需的。 服務器收到這個請求以后,會將數據放在回調函數的參數位置返回。 ```javascript foo({ 'ip': '8.8.8.8' }); ``` 由于`<script>`元素請求的腳本,直接作為代碼運行。這時,只要瀏覽器定義了`foo`函數,該函數就會立即調用。作為參數的 JSON 數據被視為 JavaScript 對象,而不是字符串,因此避免了使用`JSON.parse`的步驟。 ### WebSocket WebSocket 是一種通信協議,使用`ws://`(非加密)和`wss://`(加密)作為協議前綴。該協議不實行同源政策,只要服務器支持,就可以通過它進行跨源通信。 下面是一個例子,瀏覽器發出的 WebSocket 請求的頭信息(摘自[維基百科](https://en.wikipedia.org/wiki/WebSocket))。 ```http 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 ``` 上面代碼中,有一個字段是`Origin`,表示該請求的請求源(origin),即發自哪個域名。 正是因為有了`Origin`這個字段,所以 WebSocket 才沒有實行同源政策。因為服務器可以根據這個字段,判斷是否許可本次通信。如果該域名在白名單內,服務器就會做出如下回應。 ```http HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chat ``` ### CORS CORS 是跨源資源分享(Cross-Origin Resource Sharing)的縮寫。它是 W3C 標準,屬于跨源 AJAX 請求的根本解決方法。相比 JSONP 只能發`GET`請求,CORS 允許任何類型的請求。 下一章將詳細介紹,如何通過 CORS 完成跨源 AJAX 請求。 ## 參考鏈接 - Mozilla Developer Network, [Window.postMessage](https://developer.mozilla.org/en-US/docs/Web/API/window.postMessage) - Jakub Jankiewicz, [Cross-Domain LocalStorage](http://jcubic.wordpress.com/2014/06/20/cross-domain-localstorage/) - David Baron, [setTimeout with a shorter delay](http://dbaron.org/log/20100309-faster-timeouts): 利用 window.postMessage 可以實現0毫秒觸發回調函數
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看