同源策略那些事兒

原文首發於 同源策略那些事兒php

Tips
在本文中,指代的都是 origin,換着講純粹是出於通用的習慣。另外,出於學習的目的且爲了不瀏覽器的差別致使的麻煩,請在 Chrome 下運行如下全部客戶端的代碼。html

重現

咱們先來寫個用 ajax 提交表單的小小小的 demo,這畢竟太常見了。jquery

/Test/index.htmlajax

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>index</title>
  </head>
  <body>
    <script>
    const xhr = new XMLHttpRequest();

    xhr.open('post', 'http://127.0.0.1/Test/index.php', true);

    xhr.onreadystatechange = () => {
      if(xhr.readyState === 4 && xhr.status === 200) {
        document.write(xhr.responseText);
      }
    };

    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    xhr.send('username=yang');
    </script>
  </body>
</html>

/Test/index.phpjson

<?php
echo $_POST['username'];
?>

Tips
請將上述代碼放在本地服務器中運行。segmentfault

不出意外,你會獲得如下大禮包:跨域

XMLHttpRequest cannot load http://127.0.0.1/Test/index.php. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access.

此刻,有的人微微一笑,有的人一臉懵逼。瀏覽器

怎麼回事?

之因此會出現這個問題,是由於瀏覽器有同源策略 (Same-origin policy) 的限制,一個域 (origin) 的腳本,在未經容許的狀況下,不得經過 DOM 讀取另外一個域的文檔 (document) 的內容或屬性。緩存

同源策略在 Web 應用安全中扮演着重要的角色,它能保護一個網站的敏感信息,防止惡意腳本的竊取。同源策略中的同源,指的是協議host端口相同。同源下的文檔內容及屬性能夠共享,不一樣源下的文檔內容及屬性在未經容許時不能夠直接獲取。安全

Tips
同源中的host能夠爲主機名 (hostname) 或 IP 地址。

若是有一個地址爲:http://example.com,則:

http://example.com/test/a.html # 同源
https://example.com # 不一樣源,協議不一樣
http://www.example.com # 不一樣源,host 不一樣
http://example.com:8080 # 不一樣源,端口不一樣

規避同源策略的方法

然而咱們有些時候是須要在不一樣源的地址間進行通訊的,有如下的方法能夠用來規避同源策略。

document.domain

  • 適用狀況:不一樣的窗口 (window) 或 內斂框架元素 <iframe> 之間互相訪問文檔內容,包括 cookie
  • 實現前提:

    • 協議、端口相同
    • 當前域名 (domain name) 的父域 (superdomain) 相同
    • 其中一個窗口或 iframe 能獲得另外一個窗口或 iframe 的引用

Tips
若是兩個地址爲one.example.comtwo.example.com,則它們的父域爲example.com。注意上面所述父域不能爲頂級域名 (top-level domain),或者說不能爲一級域名 (first-level domain)。關於域名分級,詳見domain name space

無碼言*。咱們來試試。

/Test/main.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Main window</title>
  </head>
  <body>
    <p>This is the main window</p>
    <iframe src="http://w3.w1.localhost/Test/iframe.html" width="300px" height="250px" id="child-iframe" name="my"></iframe>
    <script>
      document.domain = 'w1.localhost';
    </script>
  </body>
</html>

/Test/iframe.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Iframe window</title>
  </head>
  <body>
    <p>This is the iframe window</p>
    <button id="btn">Close this iframe</button>
    <script>
    document.domain = 'w1.localhost';
    const btn = document.querySelector('#btn');
    const parent = window.parent.document;
    const frame = parent.querySelector('#child-iframe');

    btn.onclick = () => {
      parent.body.removeChild(frame);
    };
    </script>
  </body>
</html>

Tips
跨域通訊中,有些窗口屬性是容許跨域訪問的,詳見這裏

Tips
注意localhost是預留的頂級域名,不能夠把它設置爲document.domain

訪問http://w2.w1.localhost/Test/main.html,點擊按鈕,iframe 窗口消失了,再註釋掉任何一個 html 文件的 document.domain = 'w1.localhost'看看,emmmm..

另外,咱們能夠經過在服務器中以下設置Set-Cookie頭來實如今多個擁有相同父域的子域名間共享 cookie。

<?php
# 假設發起請求的域與此域是同域

# 指定了域名的話就至關於包含了全部子域名,全部子域名和此父域名均可以共享 cookie
setcookie('username', 'Sam Yang', time() + 24 * 3600, '/', "w1.localhost");
# setcookie('user', 'Sam Yang', time() + 24 * 3600, '/'); // 不指定則默認當前域名,cookie 不可被子域名共享
?>

而後你在客戶端的全部子域名下的頁面均可以經過document.cookie得到共享的 cookie。

跨文檔消息傳遞 (cross-document messaging)

  • 適用狀況:不一樣的窗口 (window) 或 內斂框架元素 <iframe> 之間跨域傳遞數據
  • 實現前提:瀏覽器支持 HTML5 的window.postMessage

經過 HTML5 提供的window.postMessage方法,可讓一個頁面的腳本,傳遞數據給另外一個頁面的腳本,而無需理會腳本所在的頁面是否跨域。

這個方法的主要語法是這樣的:

otherWindow.postMessage(message, targetOrigin)

  • otherWindow - 另外一個窗口的引用,換言之,是接收數據的窗口的引用
  • message - 發送給其餘窗口的數據,數據在發送前會被自動序列化,並且數據類型不受限制
  • targetOrigin - otherWindow的源 (origin),能夠是字符串*(能夠發送給任何源)或一個 URI,注意只有當這裏指定的targetOrigin的值和想要接收數據的窗口的源 (origin) 徹底匹配,window.postMessage觸發的事件纔會被髮送

接收數據的窗口能夠監聽message事件,這個事件接收到的數據參數包含三個重要屬性:

  • data - 發送數據的窗口傳來的數據對象
  • origin - 發送數據的窗口的源 (origin)
  • source - 發送數據的窗口的引用,能夠用這個屬性創建兩個非同源窗口間的雙向通訊

咱們來用一下:

/Test/main.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Main window</title>
  </head>
  <body>
    <p>This is the main window</p>
    <iframe src="http://w3.w1.localhost/Test/iframe.html" width="300px" height="250px" id="child-iframe" name="my"></iframe>
    <script>
      const iframe = document.querySelector('#child-iframe');
      const iframeWin = iframe.contentWindow; // 得到 iframe 元素的窗口

      iframe.onload = () => { // 等待 iframe 窗口徹底加載完再發送消息
        iframeWin.postMessage('hello, my friend', 'http://w3.w1.localhost');
      };
    </script>
  </body>
</html>

/Test/iframe.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Iframe window</title>
  </head>
  <body>
    <p>This is the iframe window</p>
    <script>
    window.onmessage = (event) => {
      if(event.origin !== 'http://w2.w1.localhost') return;

      document.write(event.data);
    };
    </script>
  </body>
</html>

訪問http://w2.w1.localhost/Test/main.html,能夠看到子窗口已經接收到了父窗口傳來的數據,你還能夠嘗試傳輸其餘更復雜的數據形式。

Tips
這個方法雖然很棒,可是須要注意如下幾點安全問題,不然你的站點可能被爆得體無完膚。

  • 不但願從其餘站點接收數據時,不要設置任何message事件的監聽器
  • 但願從其餘站點接收數據時,接收方務必使用origin屬性 (有必要的話再加上source屬性) 驗證發送者的身份,避免惡意代碼的攻擊,可保平安
  • 發送方應老是指定精確的接收方源,即targetOrigin屬性,而不是*,由於後者可讓猥瑣的站點惡意改變你發送的數據的相關屬性進而攔截你的數據

JSONP (JSON with padding)

  • 適用狀況:客戶端與服務器端的跨域通訊
  • 實現前提:無

Tips
因爲內在的風險,JSONP 正逐漸被 CORS (見下文) 取代,此處僅出於瞭解的目的講解此技巧,你能夠選擇跳過這個部分。

直譯這個東東,就是「填充的 JSON」,這是跟 Ajax) 同樣的老爺爺了,不一樣的是,前者要退休了。

這項技術之因此出現,是由於<script src="..."></script>標籤不受同源策略的限制。利用<script>元素,頁面能夠向服務器端請求 JSON 數據,服務器端收到請求後,將 JSON 數據傳傳入一個指定名字的回調函數裏再傳回給客戶端,這種使用模式就是所謂的 JSONP。

/Test/index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>index</title>
  </head>
  <body>
    <script>
    function addScriptTag(src) {
      const script = document.createElement('script');
      script.src = src;
      document.body.appendChild(script);
    }

    window.onload = () => {
      addScriptTag('http://127.0.0.1/Test/index.php?callback=sayHello'); // 這裏的 `callback` 換成其餘名字也能夠
    };
    
    function sayHello(data) { // 瀏覽器會自動解析獲得的 JSON 數據,無需手動解析
      document.body.append(`Hello, ${data.username}`);
    }
    </script>
  </body>
</html>

/Test/index.php

<?php
$callback = $_GET['callback'];
$data = array(
  'username' => 'samyang'
);
$result = json_encode($data);

echo "{$callback}({$result})";
?>

訪問http://localhost/Test/index.html

固然啦,JSONP 是很容易遭到跨站請求僞造攻擊的,因此你懂的。

WebSocket

  • 適用狀況:實時通訊 (不管是否跨域)
  • 實現前提:瀏覽器和服務器支持 HTML5 的 WebSocket

WebSocket 是一種基於 ws(非加密) / wss(加密) 協議的技術,使用這種技術能夠創建客戶端和服務器端雙向且持續的通訊鏈接,而且這不受同源策略限制。可是,一旦你使用了 WebSocket 的 URI,請求頭中就會加入`Origin字段,指明請求鏈接的腳本所在的源 (origin),服務器用這個字段來檢驗跨站請求是否安全。

這項技術仍然不穩定,但有一些成熟的相應實現,如 Socket.IO,有興趣能夠參見我寫的 Socket.IO 的教程

這項技術主要用於實時通訊,此處不作進一步詳述,想進一步探索,見 WebSocket

跨域資源共享 (Cross-Origin Resource Sharing, CORS)

好了,請出咱們的大 boss。與 JSONP 的只支持 GET 請求相比,CORS 支持全部類型的 HTTP 請求。

  • 適用狀況:客戶端與服務器端的跨域通訊
  • 實現前提:客戶端和服務器端同時支持 CORS

ajax 受到同源策略的限制,使用 ajax 技術時,在未經容許的狀況下,若是跨域請求發出給了服務器端並返回了數據 (視瀏覽器狀況而定,有的在發出時即攔截),則客戶端沒法讀取服務器端返回的數據。CORS 容許服務器端進行跨域訪問控制,從而使跨域數據傳輸得以安全進行。

咱們首先來了解下爲了支持 CORS,增長了哪些 HTTP 首部字段。

HTTP 請求首部字段

Tips
這些請求字段無需手動設置,當你使用 XMLHttpRequest 發起跨域請求時,瀏覽器已自動幫你設置好了它們。

Origin
Origin: <origin>

代表發起請求的源 (origin),這是一個 URI,不包含任何路徑信息,只是服務器的名字。

Access-Control-Request-Method
Access-Control-Request-Method: <method>

這個字段用於預檢請求 (下文會講),其做用是將實際請求所使用的 HTTP 方法告訴服務器。

Access-Control-Request-Headers
Access-Control-Request-Headers: <field-name>[, <field-name>]*

這個字段用於預檢請求,其做用是將實際請求所攜帶的自定義首部字段告訴服務器。

HTTP 響應首部字段

Access-Control-Allow-Origin
Access-Control-Allow-Origin: <origin> | *

指定能夠訪問當前資源的外源 (origin),能夠爲不含路徑信息的一個 URI,也能夠是通配符*。後者表示當前資源能夠被任何外源訪問,即容許來自全部域的請求。注意,當請求攜帶有身份憑證時 (下文講),服務器端不能夠指定該值爲通配符

Access-Control-Expose-Headers

用 XMLHttpRequest 對象的 getResponseHeader() 方法能夠獲取一些基本的響應頭,當想要獲取一些額外的響應頭時,能夠用這個字段指定。

Access-Control-Max-Age
Access-Control-Max-Age: <delta-seconds>

指定預檢請求的結果能被緩存多少秒。在這個緩存時間內,瀏覽器無須爲同一請求再次發起預檢請求。

Access-Control-Allow-Methods
Access-Control-Allow-Methods: <method>[, <method>]*

這個字段用於預檢請求響應,其指明瞭實際請求時所容許使用的 HTTP 方法。

Access-Control-Allow-Headers
Access-Control-Allow-Headers: <field-name>[, <field-name>]*

這個字段用於預檢請求響應,其指明瞭實際請求時容許攜帶的自定義首部字段。

Access-Control-Allow-Credentials

講這個字段前,咱們先講下 XMLHttpRequest 對象的 withCredentials 屬性來用,withCredentials 設置爲true時,身份憑證 (cookies、HTTP 認證信息等) 就會被瀏覽器包含在跨域請求中,設置爲false則排除在跨域請求以外而且瀏覽器忽略響應中設置 cookie 的字段,這個屬性默認爲false,也就是說通常而言,在跨域請求中,瀏覽器不會發送身份憑證信息。注意,在同源請求中,設置這個屬性沒有任何影響

Access-Control-Allow-Credentials 字段指定了當客戶端設置了 withCredentials 爲true時是否容許瀏覽器讀取響應體 (response) 的內容,爲true時表示容許。當此字段做爲預檢請求中的響應頭的一部分時,它指示的是實際請求是否可使用身份憑證 (credentials)。

當容許攜帶身份憑證時,請求頭中包含了可能含有隱私信息的 cookie 數據,因此服務器端不得設置 Access-Control-Allow-Origin 的值爲*,只能設置爲準確的域 (origin)。

Tips
雖然 CORS 容許跨域請求,可是 cookie 仍然受限於瀏覽器的同源策略,這意味着除了使用前文所講的document.domain方法外,只有來自同一個源的頁面能夠讀寫這個源的 cookie,你沒法經過 JavaScript 讀寫跨域的 cookie。設置withCredentialstrue只能讓你把跨域請求的那個服務器端設置在客戶端的 cookie 發送回給服務器端,不能讓你把客戶端設置的 cookie 發送給服務器端。詳情見這裏

Tips
當服務器端設置Set-Cookie字段時,若是設置的域名 (domain) 不包含服務器的地址,那麼設置的這個 cookie 就會被用戶代理拒絕保存。好比說你服務器端的地址爲http://w1.localhost,下面設置的 cookie 就會被用戶代理拒絕保存:

setcookie('user', 'Sam Yang', time() + 24 * 3600, '/', "w2.localhost");

詳情見 Invalid domains

簡單請求 (simple request)

不會觸發 CORS 預檢請求 (下文會提到) 的請求即爲簡單請求,具體來講,就是同時知足下列條件的請求:

  • 使用下列請求方法之一:

    • GET
    • HEAD
    • POST
  • 除了用戶代理自動設置的頭部字段,只容許手動設置如下集合中的首部字段:

    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (值爲下面三種之一)

      • text/plain
      • multipart/form-data
      • application/x-www-form-urlencoded
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
  • 請求時沒有在任何 XMLHttpRequestUpload 對象上註冊事件監聽器
  • 沒有在請求中使用 ReadableStream 對象

/Test/index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>index</title>
  </head>
  <body>
    <script>
    const xhr = new XMLHttpRequest();
    xhr.open('get', 'http://w1.localhost/Test/index.php', true);
    xhr.send();
    </script>
  </body>
</html>

/Test/index.php

<?php
header('Access-Control-Allow-Origin: *');
?>

訪問http://w2.localhost/Test/main.html,下面分別是請求頭和響應頭:

# 請求頭
GET /Test/index.php HTTP/1.1
Host: w1.localhost
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Origin: http://w2.localhost # 注意這個字段
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36
Accept: */*
DNT: 1
Referer: http://w2.localhost/Test/main.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,ja;q=0.4,zh-TW;q=0.2
HT-Ver: 1.1.2
HT-Sid: KtctXse5-mUErYXtS-aUEwI5XF-NOSNsGw+-vH+sk2L4-8852iahF-tMQulEKm-0Hvpoi9J

# 響應頭
HTTP/1.1 200 OK
Date: ****** # 已打碼
Server: Apache/2.4.25 (Unix) PHP/5.6.30
X-Powered-By: PHP/5.6.30
Access-Control-Allow-Origin: * # 注意這個字段
Content-Length: 0
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8

根據上述條件,這是一個簡單請求,咱們使用 Origin 和 Access-Control-Allow-Origin 就能完成最簡單的訪問控制。

預檢請求 (preflight request) 和 實際請求 (actual request)

爲了不那些可能對服務器數據產生反作用的 HTTP 請求方法,瀏覽器必須首先先使用 OPTIONS 方法發起一個預檢請求,從而獲知服務器端是否容許該跨域請求,得到容許以後才發起實際請求。知足下述任一條件時,即應首先發送預檢請求:

  • 使用下列請求方法之一

    • PUT
    • DELETE
    • CONNECT
    • OPTIONS
    • TRACE
    • PATCH
  • 除了用戶代理自動設置的頭部字段,手動設置瞭如下集合 以外 的首部字段:

    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (值爲下面三種之一)

      • text/plain
      • multipart/form-data
      • application/x-www-form-urlencoded
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
  • 請求時在一個 XMLHttpRequestUpload 對象上註冊了一個至多個事件監聽器
  • 在請求中使用 ReadableStream 對象

/Test/index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>index</title>
  </head>
  <body>
    <script>
    const xhr = new XMLHttpRequest();
    const body = '<?xml version="1.0"?><person><name>Arun</name></person>';

    xhr.open('post', 'http://w1.localhost/Test/index.php', true);
    xhr.setRequestHeader('X-PINGOTHER', 'pingpong');
    xhr.setRequestHeader('Content-Type', 'application/xml');

    xhr.send(body);
    </script>
  </body>
</html>

/Test/index.php

<?php
header('Access-Control-Allow-Origin: http://w2.localhost');
header('Access-Control-Allow-Headers: X-PINGOTHER, Content-Type');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
header('Access-Control-Max-Age: 86400');
?>

訪問http://w2.localhost/Test/main.html,下面分別是預檢請求頭和響應頭:

# 請求頭
OPTIONS /Test/index.php HTTP/1.1
Host: w1.localhost
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Access-Control-Request-Method: POST # 關注它
Origin: http://w2.localhost # 關注它
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36
Access-Control-Request-Headers: content-type,x-pingother # 關注它
Accept: */*
DNT: 1
Referer: http://w2.localhost/Test/main.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,ja;q=0.4,zh-TW;q=0.2
HT-Ver: 1.1.2
HT-Sid: KtctXse5-mUErYXtS-aUEwI5XF-NOSNsGw+-vH+sk2L4-8852iahF-tMQulEKm-0Hvpoi9J

# 響應頭
HTTP/1.1 200 OK
Date: ****** # 已打碼
Server: Apache/2.4.25 (Unix) PHP/5.6.30
X-Powered-By: PHP/5.6.30
Access-Control-Allow-Origin: http://w2.localhost # 關注它
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type # 關注它
Access-Control-Allow-Methods: POST, GET, OPTIONS # 關注它
Access-Control-Max-Age: 86400 # 關注它
Content-Length: 0
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8

預檢請求以後的實際請求頭和響應頭:

# 請求頭
POST /Test/index.php HTTP/1.1
Host: w1.localhost
Connection: keep-alive
Content-Length: 55
Pragma: no-cache
Cache-Control: no-cache
X-PINGOTHER: pingpong # 關注它
Origin: http://w2.localhost
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36
Content-Type: application/xml # 關注它
Accept: */*
DNT: 1
Referer: http://w2.localhost/Test/main.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,ja;q=0.4,zh-TW;q=0.2
HT-Ver: 1.1.2
HT-Sid: KtctXse5-mUErYXtS-aUEwI5XF-NOSNsGw+-vH+sk2L4-8852iahF-tMQulEKm-0Hvpoi9J

# 響應頭
HTTP/1.1 200 OK
Date: ****** # 已打碼
Server: Apache/2.4.25 (Unix) PHP/5.6.30
X-Powered-By: PHP/5.6.30
Access-Control-Allow-Origin: http://w2.localhost
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Max-Age: 86400
Content-Length: 0
Keep-Alive: timeout=5, max=99
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8

一些小伎倆

之因此叫小伎倆,是由於這裏所講的方法都只是在小數據量的跨域通訊中比較方便,大數據量則不宜使用。

  • 適用狀況:小數據量的跨域通訊

Tips
若是對 URL 的結構不甚清晰,能夠參見 URL

片斷標識符 (fragment identifier)

  • 實現前提:無。

片斷標識符是網址 URL 中#符後面的部分。咱們能夠這樣來使用之。

/Test/main.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Main window</title>
  </head>
  <body>
    <p>This is the main window</p>
    <iframe src="http://w3.w1.localhost/Test/iframe.html" width="300px" height="250px" id="child-iframe" name="my"></iframe>
  </body>
</html>

/Test/iframe.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Iframe window</title>
  </head>
  <body>
    <p>This is the iframe window</p>
    <script>
    window.onhashchange = () => {
      document.write(`hello, ${window.location.hash.substr(1)}`);
    };
    </script>
  </body>
</html>

訪問http://w2.w1.localhost/Test/main.html,打開控制檯,輸入:

document.querySelector('#child-iframe').src += '#samyang'

emmmm... 小窗口內容變了!

可是呢,這種方法是比較雞肋的,雖然#有其餘的用途 (見 這裏),但它通常是用於定位頁面中某個部分的,若是頁面中有一些標籤含有相同的片斷標識符,便會產生一些意料以外的行爲。再者,這種方法傳輸的數據量太少了。

查詢字符串 (search string, aka, query string)

  • 實現前提:無。

這是另外一種用於小數據量跨域通訊的方法,也是我所經常使用的方法,也是相比於前者更推薦的作法。查詢字符串是 URL 中?及其後面但不包括#及片斷標識符的部分。

/Test/main.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Main window</title>
  </head>
  <body>
    <p>This is the main window</p>
    <button>Click me</button>
    <script>
      document.querySelector('button').onclick = () => {
        location.href = `http://w3.w1.localhost/Test/iframe.html?username=samyang`;
      };
    </script>
  </body>
</html>

/Test/iframe.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Iframe window</title>
  </head>
  <body>
    <p>This is the iframe window</p>
    <button id="btn">Close this iframe</button>
    <script>
    window.onload = () => {
      document.write(location.search.substr(1))
    };
    </script>
  </body>
</html>

訪問http://w2.w1.localhost/Test/main.html,點擊按鈕。

window.name

  • 實現前提:跨域頁面在同一個窗口中。

這個屬性是屬於窗口的,只要窗口不變,即便頁面變爲不一樣域,這個屬性的值也不變。

/Test/main.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Main window</title>
  </head>
  <body>
    <p>This is the main window</p>
    <script>
      window.name = 'samyang';
      location.href = 'https://www.sogou.com/';
    </script>
  </body>
</html>

訪問http://w2.w1.localhost/Test/main.html,窗口跳到新頁面後打開控制檯輸入window.name並回車能夠看到原頁面設置的值。

結尾

好了,大概是這麼多了(其實還有不少),若有疏漏請指出。

Reference

相關文章
相關標籤/搜索