前言
從“跨域”這個詞開始,去理清跨域這個知識點,途徑同源策略,跨過document.domain,window.postMessage,JSONP,CORS等,先放若干個問題,希望看完文章的你可以答上來,
- 能說說跨域嗎?
- 能說說同源策略嗎?
- 為什么要同源策略,它限制了什么?
- 你知道哪些跨域方案呢?
- 有關cookie的跨域怎么實作?
- 能具體說說JSONP嗎?回傳什么資料呢,前端怎么處理呢?知道什么原理嗎?實作過嗎?JSONP服務器端實作過嗎?
- postMessage 了解嗎?怎么使用?需要注意什么?(安全方面)
- 代理了解過嗎?用過哪些代理方案呢不?怎么在專案中用呢?
- cors可以具體說一個簡單請求和非簡單請求嗎?具體程序說一下?專案中怎么使用?
文章可能有些地方寫的不當和不全的地方,歡迎評論區給我建議,感謝~~ 🤞🤞🤞
也希望里面的知識點有哪里不清楚的,你可以自己可以花時間去整明白更好,加油呀😊
這次就不放導圖啦,右邊目錄很清楚~~
1、講一下跨域是什么?
一個源加載的檔案或者腳本和來自另一個源的檔案和腳本等資源進行互動(也就是不滿足同源策略的兩個源之間進行一些互動),就是跨域,
所以你需要清楚的是同源策略是什么?它為什么出現?它又限制了什么? 往下看吧:
2、同源策略
2.1、 同源策略是什么?
所謂"同源"指的是"三個相同",
- 協議相同
- 域名相同
- 埠相同
舉個栗子:
http://www.jingda.com/dir/page.html這個網址,協議是http://,域名是www.jingda.com,埠是80(默認埠可以省略),來看看下面改編的哪些是同源哪些是不同源:
http://www.jingda.com/dir2/other.html:同源http://jingda1.com/dir/other.html:不同源(域名不同)http://v2.www.jingda.com/dir/other.html:不同源(域名不同)http://www.jingda.com:81/dir/other.html:不同源(埠不同)https://www.jingda.com/dir/page.html:不同源(協議不同)
2.2、 為什么需要同源策略?
同源政策的目的,是為了保證用戶資訊的安全,防止惡意的網站竊取資料,它能幫助阻隔惡意檔案,減少可能被攻擊的媒介, 假設小明同學在A銀行的官網進行了登錄,之后他又去瀏覽了其他網站,如果其他網站可以讀取A銀行官網的cookie,那么小明在A銀行的登錄資訊和其他存款記錄等都會被泄露,將是一件非常危險的情況,
而cookie的訪問限制只是同源策略限制的一種情況,下面我們介紹一下其他的,
2.3、 同源策略帶來了什么訪問限制?
-
跨源資料存盤訪問:訪問存盤在瀏覽器中的資料,如 localStorage 和 IndexedDB,是以源進行分割;Cookies 使用不同的源定義方式,每個源都擁有自己單獨的存盤空間,一個源中的 JavaScript 腳本不能對屬于其它源的資料進行讀寫操作,
-
跨源腳本API訪問:JavaScript 的 API 中,如 iframe.contentWindow、 window.parent、window.open 和 window.opener 允許檔案間直接相互參考,當兩個檔案的源不同時,這些參考方式將對 Window 和 Location物件的訪問添加限制,
-
跨源網路訪問:同源策略控制不同源之間的互動,例如在使用XMLHttpRequest 或 [圖片上傳中…(image-d026b6-1618640180825-0)]
標簽時則會受到同源策略的約束,
3、解決跨域的幾種方法?
將上面三種訪問限制簡化成下面的三種表達:
(1) Cookie、LocalStorage 和 IndexDB 無法讀取,
(2) JavaScript 的 API 中的一些參考,無法獲得,(詳見上)
(3) AJAX 請求不能發送,(也就是無法使用XMLHttpRequest)
(因為在網上有關跨域的解決方案,可能是比較多,但這里我是根據上面三種限制依次介紹一下可能行得通的解決方案)
3.1、 cookie – document.domain
當我們嘗試解決因同源策略下,無法訪問cookie這種情況時,我們可以借助:
- 1、
瀏覽器允許通過設定document.domain共享 Cookie,來達成效果,但是,兩個網頁一級域名相同,只是二級域名不同才可以設定,那什么是一級域名,什么是二級域名呢?
舉個栗子: A網頁:http://w1.jingda.com/a.html 在這個網頁地址中,w1.jingda.com這部分統稱為域名,
- 一級域名是由一個合法的字串+域名后綴組成,所以,jingda.com這種形式的域名才是一級域名,jingda是域名主體,.com、.net也是域名后綴,
- 二級域名實際就是一級域名下面的主機名,顧名思義,
它是在一級域名前面加上一個字串,比如w1.jingda.com,
解釋完怎樣的情況可以設定document.domain共享 Cookie,讓我們看看一個如何操作:
假設有兩個網頁地址,我們可以看到,他們的一級域名是相同的,二級域名的不同的:
A網頁:http://w1.jingda.com/a.html
B網頁:http://w2.jingda.com/b.html
那么只要設定相同的document.domain,兩個網頁就可以共享Cookie,
document.domain = 'example.com';
復制代碼
A網頁通過腳本設定一個 Cookie,
document.cookie = "test1=hello";
復制代碼
B網頁就可以讀到這個 Cookie,
var allCookie = document.cookie;
復制代碼
2、服務器也可以在設定Cookie的時候,指定Cookie的所屬域名為一級域名,比如.example.com,
Set-Cookie: key=value; domain=.example.com; path=/
復制代碼
這樣的話,二級域名和三級域名不用做任何設定,都可以讀取這個Cookie,
這里的話,補充一下設定cookie的時候,一些其他的設定來限定其可訪問性:
- Domain 和 Path 標識定義了Cookie的作用域:即允許 Cookie 應該發送給哪些URL,
- Secure:Secure屬性是說如果一個cookie被設定了Secure=true,那么這個cookie只能用https協議發送給服務器,用http協議是不發送的,
- HttpOnly :使用 HttpOnly 屬性可防止通過 JavaScript 訪問 cookie 值
- SameSite Cookie 允許服務器要求某個 cookie 在跨站請求時不會被發送,從而可以阻止跨站請求偽造攻擊(CSRF),
你應該注意到,這里我們只是單單解決了在有一些限制條件下的訪問cookie的限制,但是上面還提到的LocalStorage 和 IndexDB暫時還沒有解決,(等下再說)
3.2、 API訪問 – window.postMessage
postMessage是html5新增的一個解決跨域的一個方法,為了能讓不同源中檔案進行交流,可以使用 window.postMessage安全地實作跨源通信,(安全是指在正確的使用情況下),這
3.2.1、window.postMessage的使用場景?
這個我自己也是沒有用過的,使用方法大家可以參考這篇window.postMessage用法一個比較小的案例
因為是兩個視窗頁面之間的通信,因此我們這邊假設我兩個頁面,A,B,目的是在B視窗中點擊postMessage按鈕,能夠在A頁面收到發來的訊息, A頁面:
<script>
function test() {
let op = window.open('b.html', '_blank');
function receiveMessage(event) {
console.log('event', event);
}
op.addEventListener("message", receiveMessage, false);
}
</script>
<body>
<div>
<button onClick="test()">open</button>
</div>
</body>
復制代碼
B頁面:
<script>
function post() {
window.postMessage("hi there!", location.origin);
}
function receiveMessage(event) {
console.log('event', event)
}
window.addEventListener("message", receiveMessage, false);
</script>
<body>
<div>
<button onClick="post()">postMessage</button>
</div>
</body>
復制代碼
我就直接說一下大概的思路了: 首先看看B頁面:
- 在B頁面有一個按鈕,點擊這個按鈕會觸發一個方法,post()
- 在post()方法中,window.postMessage(“hi there!”, location.origin),發送到所有同源的視窗,注意,當前視窗也會收到
- 之后通過 window.addEventListener(“message”, receiveMessage, false)去監聽,如果有資料,就執行receiveMessage(),把資料列印出來
再來看A頁面:
- 在A頁面也有一個按鈕,當點擊這個按鈕時觸發test()
- 打開新視窗,并建立視窗的參考變數op = window.open(‘B.html’, ‘_blank’);
- op.addEventListener(“message”, receiveMessage, false); 監聽新開視窗發來的訊息,通過 receiveMessage() 把資料列印出來
3.2.2、如何正確的使用,以保證安全性?
- 始終使用origin和source屬性驗證發件人的身份,沒有驗證origin和source屬性會導致跨站點腳本攻擊,
- 當使用postMessage將資料發送到其他視窗時,指定精確的目標origin,而不是*
3.3、JSONP
JSONP(JSON with Padding)是JSON的一種“使用模式”,可用于解決主流瀏覽器的跨域資料訪問的問題,
3.3.1、JSONP的介紹
JSONP 是通過在<script></srcipt>標簽里,通過src,img,href 屬性的跨域方式向一個不同源的網站地址發送http請求,并且使得json資料可以在javascript代碼中能夠使用,
它規避了javascript代碼中的跨源網路訪問,也就是無法使用XMLHttpRequest,fetch被同源機制管到了(如果不同源的話),
提前準備一個介面:https://photo.sina.cn/aj/index?page=1&cate=recommend 直接網頁中打開,我們是可以看到有很多資料的,如下圖:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-VOqT1VMJ-1618820609526)(https://upload-images.jianshu.io/upload_images/23129380-7f4e1aed616cbfa6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
讓我們嘗試在本地請求一下這個地址,看看能不能拿到資料:因為雙方地址并不是同源的,因此這樣請求會報跨域的錯:
<body>
<script>
fetch('https://photo.sina.cn/aj/index?page=1&cate=recommend')
.then(data=>{
console.log(data);
})
</script>
</body>
復制代碼
通過 live-server打開瀏覽器,在控制臺可以看到報錯了,因為這個是一個跨域的請求:

接下來我們來看看JSONP如何解決這個問題:
3.3.2、jsonp 如何使用?原理是什么?回傳資料格式?前端怎么處理?
還是請求上面的這個網站地址,我們把代碼改成下面這樣:
<body>
<script>
function callback(data){
console.log(data);
}
</script>
<script src="https://photo.sina.cn/aj/index?page=1&cate=recommend&callback=callback"></script>
</body>
復制代碼
再來看看頁面控制臺輸出:

data成功取到了,但是我們的資料到達之后是json資料,不能直接使用,script標簽是一個加載資源的標簽,它并不能直接運行這個代碼,
事實上我們是在訪問的時候,在請求的地址后面加上一個,&callback=callback,通知服務器,本地想進行一個跨資源訪問(以JSOP的形式進行跨域),等號后面的callback是一個你自己定義的函式,名字可自取,這個函式就是,通知我需要請求的地址,這邊頁面上我有一個函式,它會等待呼叫,用來執行你發過來的資料(也就是可以去執行把資料請求下來的操作),
因此在資料到達之后,還包了一層函式 callback({data}),當資料通過script標簽請求下來之后,再通過callback實作了一個呼叫本地資源的能力,

最后再理一下這部分的內容:
- JSONP的原理
script標簽請求資料,在請求的地址后面加上一個,&callback=callback,請求的服務器就在json資料外面包一層callback函式,當這個帶有資料的callback函式可以在script得到之后可以運行的函式:

- 回傳的資料格式
JSON
- 以及前端如何處理的
JSON with padding — callback({data})
3.3.3、自己封裝一個jsonp?
- 準備作業
<script>
let jsonp = () => {
}
jsonp('https://photo.sina.cn/aj/index', {
page: 1,
cate: 'recommend'
})
.then(response => {
console.log(response,'呼叫成功啦');
})
</script>
復制代碼
- 具體實作流程
- 確定傳遞引數: url 、攜帶的引數 、callback;
- 處理url上的引數(?后面的);
- 準備好url(攜帶callback函式);
- 構建script標簽;
- 把這個標簽掛到window上
<script>
// 1、確定好引數
let jsonp = (url,data = {},callback = 'callback') => {
// 2、處理好url里面的引數
let dataStr = url.indexOf('?') === -1 ? '?':'&'
// 3、把引數和&拼接上去
for (let key in data) {
dataStr += `${key}=${data[key]}&`;
}
// 4、把callback拼接上
dataStr += 'callback=' + callback;
// 5、創建一個script標簽
let oScript = document.createElement('script');
oScript.src = url + dataStr;
document.body.appendChild(oScript);
// 6、把script標簽掛載到window上去
//方案一、
// window[callback] = (data) => {
// console.log(data);
// }
// 方案二、
return new Promise((reslove,reject) => {
window[callback] = (data) => {
try {
reslove(data)
} catch(e) {
reject(e)
} finally {
oScript.parentNode.removeChild(oScript);
//洗掉這個script節點
}
}
})
}
//呼叫jsonp方法
jsonp('https://photo.sina.cn/aj/index?a=1', {
page: 1,
cate: 'recommend'
})
.then(response => {
console.log(response,'呼叫成功啦');
})
</script>
復制代碼
3.3.4、實作一個jsonp服務器端?(node版本,express版本)
node版本
創建一個結構如下的服務器端檔案夾,我們將在index.js中實作我們的JSONP: 
var http = require('http');
http.createServer(function(req, res){
// req url callback=?
console.log(req.url);
let data = {a: 1};
res.writeHead(200, {'Content-type' : 'text/json'})
const reg = /callback=([\w]+)/
if (reg.test(req.url)) {
let padding = RegExp.$1
res.end(`${padding}(${JSON.stringify(data)})`)
} else {
res.end(JSON.stringify(data));
}
// res.end('<p>Hello World</p>');
res.end(JSON.stringify(data));
}).listen(3000);
復制代碼
express 版本
var express = require('express');
var cors = require('cors');//后端cors 中間件
const app = express();
app.use(cors());
app.get('/product',(req,res)=>{
res.json({
a:1,
b:2
})
})
app.listen(8000,()=>{
console.log('server is ok')
})
復制代碼
3.4 cors
3.4.1、介紹一下cors?
CORS是一個W3C標準,全稱是"跨域資源共享"(Cross-origin resource sharing),它允許瀏覽器向跨源服務器,發出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制,
3.4.2、簡單請求和非簡單請求?
瀏覽器將CORS請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request),
除了簡單請求其他的都是非簡單請求,因此只要記住哪些是簡單請求就可以啦:
簡單請求:(需要同時滿足下面兩種條件)
- 請求方法是以下三種方法之一:
- HEAD
- GET
- POST
- HTTP的頭資訊不超出以下幾種欄位:
- Accept:設定接受的內容型別(請求頭)
- Accept-Language:設定接受的語言(請求頭)
- Content-Language:為封閉內容設定自然語言或者目標用戶語言(回應頭)
- Content-Type:(設定請求體的MIME型別(適用POST和PUT請求))只限于三個值
application/x-www-form-urlencoded:
中默認的encType,form表單資料被編碼為key/value格式發送到服務器(表單默認的提交資料的格式)
multipart/form-data:將表單的資料處理為一條訊息,以標簽為單元,用分隔符分開,既可以上傳鍵值對,也可以上傳檔案,
text/plain:text/plain :純文本格式
3.4.3、專案中怎么使用?
- 服務器端:
const express = require('express');
const app= express();
app.get('/', (req, res)=>{
console.log('server is OK');
res.end('jingjing')
});
// app.use((req, res, next) => {
// res.header("Access-Control-Allow-Origin",'http://localhost:5500');
// res.header("Access-Control-Allow-Credentials", true);
// res.header("Access-Control-Allow-Headers", 'Content-Type,Content-Length,Authorization, Accept,X-Requested-With');
// res.header("Access-Control-Allow-Methods", 'PUT,POST,GET,DELETE,OPTIONS,HEAD');
// req.method === 'OPTIONS' ? res.send('CURRENT SERVICES SUPPORT CROSS DOMAIN REQUESTS!') : next();
// });
app.listen(8081, ()=>{
console.log('Server is running at http://localhost:8081')
})
復制代碼
- 前端請求:
<body>
<button onclick="sendAjax()">sendAjax</button>
<script type="text/javascript">
var sendAjax = () => {
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://127.0.0.1:5500', true);
xhr.send();
xhr.onreadystatechange = function (e) {
if (xhr.readyState == 4 && xhr.status == 200) {
console.log(xhr.responseText);
console.log('成功了')
}
};
}
</script>
</body>
復制代碼
跨域報錯:

把中間注釋的部分放開再執行:沒有上面的報錯了,也回傳了
console.log(xhr.responseText);
console.log('成功了')

分析一下:
- “Access-Control-Allow-Origin”,
http://localhost:5500:
如果服務端僅允許來自 http://localhost:5500的訪問,如果服務端回傳的 Access-Control-Allow-Origin: * 表明,該資源可以被任意外域訪問,
- “Access-Control-Allow-Credentials”, true):
Access-Control-Allow-Credentials 頭指定了當瀏覽器的credentials設定為true時是否允許瀏覽器讀取response的內容,
- “Access-Control-Allow-Headers”, ‘Content-Type,Content-Length,Authorization, Accept,X-Requested-With’):
首部欄位 Access-Control-Allow-Headers 表明服務器允許請求中攜帶欄位 X-PINGOTHER 與 Content-Type,
- “Access-Control-Allow-Methods”, ‘PUT,POST,GET,DELETE,OPTIONS,HEAD’:
首部欄位 Access-Control-Allow-Methods 表明服務器允許客戶端使用 POST, GET 和 OPTIONS 等方法發起請求
- req.method === ‘OPTIONS’ ? res.send(‘CURRENT SERVICES SUPPORT CROSS DOMAIN REQUESTS!’) : next():
“需預檢的請求”要求必須首先使用 OPTIONS 方法發起一個預檢請求到服務器,以獲知服務器是否允許該實際請求(除簡單請求以外的,比如 POST方法就需要用到預檢)
3.5、代理 (nginx)
3.5.1 原理
A網站向B網站請求1.js檔案時,向B網站發送一個獲取的請求,nginx根據組態檔接收這個請求,代替A網站向B網站來請求這個資源,nginx拿到這個資源后再回傳給a網站,以此來解決了跨域問題,

3.5.2 使用
使用Nginx,有關下載和配置Nginx,我就不再這里說了,感興趣的小伙伴可以參考一下這篇文章,里面配置相關的講的比較清楚,正確的Nginx跨域配置
(自己平時也沒怎么用就是,唉,大多知識點也是一邊寫一邊理)
但是的但是,學習還是要學滴,回到最開始我們提到的一些問題,來看看你能回答多少 👇👇👇
總結
最后再來一次拷問:
- 能說說跨域嗎?
- 能說說同源策略嗎?
- 為什么要同源策略,它限制了什么?
- 你知道哪些跨域方案呢?
- 有關cookie的跨域怎么實作?
- 能具體說說JSONP嗎?回傳什么資料呢,前端怎么處理呢?知道什么原理嗎?實作過嗎?JSONP服務器端實作過嗎?
- postMessage 了解嗎?怎么使用?需要注意什么?(安全方面)
- 代理了解過嗎?用過哪些代理方案呢不?怎么在專案中用呢?
- cors可以具體說一個簡單請求和非簡單請求嗎?具體程序說一下?專案中怎么使用?
🙈
最后(相關面試題)
因為有拷問題了,拷問題大家能回答上也是掌握啦,這次的面試題小編就以PDF形式展示給大家,不然篇幅被限制了,面試題出不來啦,需要前端面試題完整版PDF的,點擊這里直接領取:


好啦,有關跨域的就梳理到這里了,逐點突破系列也更新到第二期了,小編更新的慢小伙伴們不要建議哈,這個系列你想知道那個知識點也可以告訴我哦,我們一起來“逐點突破”!
你的一鍵三連是我最大的支持 你的評論是我最大鼓勵🤞 逐點突破,突破自我!我們下期見!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/278028.html
標籤:其他
上一篇:web安全常見的加密編碼進制
