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