前端百題斬之原來(lái)跨域也是可以進(jìn)行分類(lèi)的
25.1 同源策略
25.1.1 同源
跨域本質(zhì)其實(shí)就是指兩個(gè)地址不同源,不同源的反面不就是同源,同源指的是:如果兩個(gè)URL的協(xié)議、域名和端口號(hào)都相同,則就是兩個(gè)同源的URL。
- // 非同源:協(xié)議不同
- http://www.baidu.com
- https://www.baidu.com
- // 同源:協(xié)議、域名、端口號(hào)都相同
- http://www.baidu.com
- http://www.baidu.com?query=1
25.1.2 同源策略
同源策略是一個(gè)重要的安全策略,它用于限制一個(gè)origin的文檔或者它加載的加載的腳本如何能與另一個(gè)源的資源進(jìn)行交互。其主要是為了保護(hù)用戶(hù)信息的安全,防止惡意的網(wǎng)站竊取數(shù)據(jù),是瀏覽器在Web頁(yè)面層面做的安全保護(hù)。
25.1.3 同源策略的表現(xiàn)
既然同源策略是瀏覽器在Web頁(yè)面層面做的保護(hù),那么該層面哪些位置需要進(jìn)行保護(hù)呢?總結(jié)下來(lái)主要包含三個(gè)層面:DOM層面、數(shù)據(jù)層面、網(wǎng)絡(luò)層面。
DOM層面
同源策略限制了來(lái)自不同源的JavaScript腳本對(duì)當(dāng)前DOM對(duì)象讀和寫(xiě)的操作。
數(shù)據(jù)層面
同源策略限制了不同源的站點(diǎn)讀取當(dāng)前站點(diǎn)的Cookie、IndexedDB、localStorage等數(shù)據(jù)。
網(wǎng)絡(luò)層面
同源策略限制了通過(guò)XMHttpRequest等方式將站點(diǎn)的數(shù)據(jù)發(fā)送給不同源的站點(diǎn)。
25.2 跨域分類(lèi)
同源策略保證了瀏覽器的安全,但是如果將這三個(gè)層面限制的死死的,則會(huì)讓程序員的開(kāi)發(fā)工作舉步維艱,所以瀏覽器需要在最嚴(yán)格的同源策略限制下做一些讓步,這些讓步更多了是在安全性與便捷性的權(quán)衡。其實(shí)跨域的方式就可以認(rèn)為是瀏覽器出讓了一些安全性或在遵守瀏覽器同源策略前提下所采取的一種折中手段。
25.2.1 DOM層面和數(shù)據(jù)層面分類(lèi)
根據(jù)同源策略,如果兩個(gè)頁(yè)面不同源,無(wú)法互相操作DOM、訪(fǎng)問(wèn)數(shù)據(jù),但是兩個(gè)不同源頁(yè)面之間進(jìn)行通信是比較常見(jiàn)的情形,典型的例子就是iframe窗口與父窗口之間的通信。隨著歷史的車(chē)輪,實(shí)現(xiàn)DOM層面間通信的方式有多種,如下所示:
片段標(biāo)識(shí)符
片段標(biāo)識(shí)符其核心原理就是通過(guò)監(jiān)聽(tīng)url中hash的改變來(lái)實(shí)現(xiàn)數(shù)據(jù)的傳遞,想法真的很巧妙。
- // 父頁(yè)面parentHtml.html
- <!DOCTYPE html>
- <html lang="zh">
- <head>
- <title></title>
- </head>
- <body>
- 我是父頁(yè)面
- <button id='btn'>父?jìng)鹘o子</button>
- <iframe src="./childHtml.html" id="childHtmlId"></iframe>
- </body>
- <script>
- window.onhashchange = function() {
- console.log(decodeURIComponent(window.location.hash));
- };
- document.getElementById('btn').addEventListener('click', () => {
- const iframeDom = document.getElementById('childHtmlId');
- iframeDom.src += '#父?jìng)鹘o子';
- });
- </script>
- </html>
- // 子頁(yè)面childHtml.html
- <!DOCTYPE html>
- <html lang="zh">
- <head>
- <title></title>
- </head>
- <body>
- 我是子頁(yè)面
- <button id='btn'>子傳給父</button>
- </body>
- <script>
- window.onhashchange = function() {
- console.log(decodeURIComponent(window.location.hash));
- };
- document.getElementById('btn').addEventListener('click', () => {
- parent.location.href += '#子傳給父';
- });
- </script>
- </html>
window.name
瀏覽器窗口有window.name屬性,這個(gè)屬性的最大特點(diǎn)是,無(wú)論是否同源,只要在同一個(gè)窗口里,前一個(gè)網(wǎng)頁(yè)設(shè)置了這個(gè)屬性,后一個(gè)網(wǎng)頁(yè)可以讀取它。如果需要實(shí)現(xiàn)父頁(yè)面和跨域的子頁(yè)面之間的通信,需要一個(gè)和父頁(yè)面同源的子頁(yè)面作為中介,將跨域的子頁(yè)面中的信息傳遞過(guò)來(lái)。(好麻煩呀,強(qiáng)烈不推薦使用,此處就不寫(xiě)對(duì)應(yīng)的代碼啦)
document.domain
document.domain是存放文檔的服務(wù)器的主機(jī)名,可通過(guò)手動(dòng)設(shè)置將其設(shè)置成當(dāng)前域名或者上級(jí)的域名,當(dāng)具有相同document.domain的頁(yè)面就相當(dāng)于處于同域名的服務(wù)器上,如果其域名和端口號(hào)相同就可以實(shí)現(xiàn)跨域訪(fǎng)問(wèn)數(shù)據(jù)了。
postMessage(強(qiáng)烈推薦)
window.postMessage是HTML5新增的跨文檔通信API,該API,允許跨窗口通信,不論這兩個(gè)窗口是否同源。
- // 父頁(yè)面
- <!DOCTYPE html>
- <html lang="zh">
- <head>
- <title></title>
- </head>
- <body>
- 我是父頁(yè)面
- <button id='btn'>父?jìng)鹘o子</button>
- <iframe src="http://127.0.0.1:5500/024/childHtml.html" id="childHtmlId"></iframe>
- </body>
- <script>
- window.addEventListener('message', function(event) {
- console.log('父頁(yè)面接收到信息', event.data);
- });
- document.getElementById('btn').addEventListener('click', () => {
- const iframeDom = document.getElementById('childHtmlId');
- iframeDom.contentWindow.postMessage('我是執(zhí)鳶者1', 'http://127.0.0.1:5500/024/childHtml1.html');
- });
- </script>
- </html>
- // 子頁(yè)面
- <!DOCTYPE html>
- <html lang="zh">
- <head>
- <title></title>
- </head>
- <body>
- 我是子頁(yè)面
- <button id='btn'>子傳給父</button>
- </body>
- <script>
- window.addEventListener('message', function(event) {
- console.log('子頁(yè)面接收到信息', event.data);
- });
- document.getElementById('btn').addEventListener('click', () => {
- parent.postMessage('我是執(zhí)鳶者2', 'http://127.0.0.1:5500/024/parentHtml1.html');
- });
- </script>
- </html>
25.2.2 網(wǎng)絡(luò)層面
根據(jù)同源策略,瀏覽器默認(rèn)是不允許XMLHttpRequest對(duì)象訪(fǎng)問(wèn)非同一站點(diǎn)的資源的,這會(huì)大大制約生產(chǎn)力,所以需要破解這種限制,實(shí)現(xiàn)跨域訪(fǎng)問(wèn)資源。目前廣泛采用的主要有三種方式(注:該出不給出具體代碼,后續(xù)會(huì)有專(zhuān)門(mén)的百題斬進(jìn)行詳細(xì)闡述):
通過(guò)代理實(shí)現(xiàn)
同源策略是瀏覽器為了安全制定的策略,所以服務(wù)端不會(huì)存在這樣的限制,這樣我們就可以將請(qǐng)求打到同源的服務(wù)器上,然后經(jīng)由同源服務(wù)器代理至最終需要的服務(wù)器,從而實(shí)現(xiàn)跨域請(qǐng)求的目的。例如可以通過(guò)Nginx、Node中間件等。
JSONP的方式(具體實(shí)現(xiàn)見(jiàn)后續(xù)百題斬)
JSONP是一種借助script元素實(shí)現(xiàn)跨域的技術(shù),它并沒(méi)有使用XMLHttpRequest對(duì)象,其能夠?qū)崿F(xiàn)跨域主要得益于script有兩個(gè)特點(diǎn):
(1)src屬性能夠訪(fǎng)問(wèn)任何URL資源,并不會(huì)受到同源策略的限制;
(2)如果訪(fǎng)問(wèn)的資源包含JavaScript代碼,其會(huì)在下載后自動(dòng)執(zhí)行。
CORS方式(具體實(shí)現(xiàn)見(jiàn)后續(xù)百題斬)
跨域資源共享(CORS),該機(jī)制可以進(jìn)行跨域訪(fǎng)問(wèn)控制,從而使跨域數(shù)據(jù)傳輸?shù)靡园踩M(jìn)行。(實(shí)現(xiàn)一個(gè)跨域請(qǐng)求的方式,其中html訪(fǎng)問(wèn)網(wǎng)址為http://127.0.0.1:8009; 服務(wù)器監(jiān)聽(tīng)端口為:8010)
(1)html頁(yè)面內(nèi)容
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <title>test CORS</title>
- </head>
- <body>
- CORS
- <script src="https://code.bdstatic.com/npm/axios@0.20.0/dist/axios.min.js"></script>
- <script>
- axios('http://127.0.0.1:8010', {
- method: 'get'
- }).then(console.log)
- </script>
- </body>
- </html>
(2)服務(wù)器端代碼
- const express = require('express');
- const app = express();
- app.get('/', (req, res) => {
- console.log('get請(qǐng)求收到了!??!');
- res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:8009');
- res.send('get請(qǐng)求已經(jīng)被處理');
- })
- app.listen(8010, () => {
- console.log('8010 is listening')
- });
本文轉(zhuǎn)載自微信公眾號(hào)「執(zhí)鳶者」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系執(zhí)鳶者公眾號(hào)。