什么是瀏覽器同源策略?如何處理同源策略帶來的跨域問題?
原文合集地址如下,有需要的朋友可以關注
本文地址
合集地址
什么是瀏覽器同源策略
瀏覽器的同源策略(Same-Origin Policy)是一種安全機制,用于限制一個網(wǎng)頁文檔或腳本如何與來自不同源的資源進行交互。同源是指兩個 URL 的協(xié)議、主機和端口號都相同。
同源策略的目的是保護用戶的隱私和安全。它可以防止惡意網(wǎng)站通過腳本訪問其他網(wǎng)站的敏感信息或進行惡意操作。同源策略主要限制以下幾個方面的交互:
跨域資源讀?。涸跒g覽器中,一個網(wǎng)頁只能通過 AJAX、WebSocket 或 Fetch API 等方式來請求同源網(wǎng)站的數(shù)據(jù)。這意味著腳本無法直接讀取來自其他域的數(shù)據(jù),以防止惡意網(wǎng)站獲取用戶的敏感信息。
跨域資源加載:瀏覽器中的腳本無法直接加載來自其他域的資源,如 JavaScript 文件、CSS 文件或字體文件。這是為了防止惡意腳本篡改其他域的資源或執(zhí)行惡意代碼。
跨域窗口通信:瀏覽器中的腳本只能與同源窗口進行通信,不能直接操作或獲取來自其他域的窗口對象的內(nèi)容。
同源策略通過限制不同源之間的交互,提高了瀏覽器的安全性。然而,有時需要在不同源之間進行數(shù)據(jù)交換,為此引入了一些跨域解決方案,如跨域資源共享(CORS)和跨文檔消息傳遞(PostMessage)。這些解決方案允許在特定條件下進行跨域交互,同時保持了一定的安全性。
如何解決跨域問題
跨域問題是由瀏覽器的同源策略所引起的,它限制了不同源(協(xié)議、域名、端口)之間的資源交互。要解決跨域問題,可以采取以下幾種方法:
JSONP(JSON with Padding):JSONP是一種利用
<script>
標簽不受同源策略限制的特性來實現(xiàn)跨域請求的方法。通過在請求URL中添加一個回調(diào)函數(shù)參數(shù),服務器返回的響應將被包裹在該函數(shù)中,從而實現(xiàn)跨域數(shù)據(jù)的獲取。不過用的少。 下面是一個簡單的示例,演示如何使用 JSONP 進行跨域數(shù)據(jù)獲?。?/p>
假設網(wǎng)頁位于 http://www.example.com
,希望從跨域的服務器 http://api.example.com
獲取數(shù)據(jù)。
在客戶端的 HTML 頁面中添加以下代碼:
<html>
<head>
????<title>JSONP?Example</title>
????<script>
????????function?handleResponse(data)?{
????????????console.log(data);?//?在控制臺中打印獲取到的數(shù)據(jù)
????????}
????</script>
</head>
<body>
????<script?src="http://api.example.com/data?callback=handleResponse"></script>
</body>
</html>
在這個例子中,在頁面中定義了一個名為 handleResponse
的函數(shù)來處理獲取到的數(shù)據(jù)。然后通過添加 <script>
標簽來請求跨域服務器上的數(shù)據(jù),并在 URL 的查詢參數(shù)中指定回調(diào)函數(shù)的名稱為 handleResponse
。
在跨域服務器
http://api.example.com
上,根據(jù)請求的 URL 參數(shù)返回相應的數(shù)據(jù)。服務器端代碼可以是以下示例(使用 Node.js 和 Express 框架):
const?express?=?require('express');
const?app?=?express();
app.get('/data',?(req,?res)?=>?{
????const?data?=?{?message:?'Hello,?World!'?};
????const?callbackName?=?req.query.callback;?//?獲取回調(diào)函數(shù)名稱
????//?返回數(shù)據(jù),并將數(shù)據(jù)包裹在回調(diào)函數(shù)中
????res.send(`${callbackName}(${JSON.stringify(data)})`);
});
app.listen(80,?()?=>?{
????console.log('Server?started');
});
在這個例子中,當客戶端發(fā)起 GET 請求到 /data
路由時,服務器獲取回調(diào)函數(shù)的名稱,然后將數(shù)據(jù)包裹在回調(diào)函數(shù)中作為響應返回給客戶端。
通過這種方式,客戶端就能夠從跨域服務器上獲取數(shù)據(jù),并在指定的回調(diào)函數(shù)中進行處理。 例子僅供參考
CORS(Cross-Origin Resource Sharing):CORS是一種在服務器端配置的解決方案。通過在響應頭中添加特定的跨域策略信息,允許瀏覽器在跨域請求時獲取和處理來自其他域的數(shù)據(jù)。服務器需要設置適當?shù)?code>Access-Control-Allow-Origin、
Access-Control-Allow-Methods
、Access-Control-Allow-Headers
等響應頭來指定允許的跨域訪問規(guī)則。 以下是一個示例,演示如何使用CORS來允許跨域訪問: 假設網(wǎng)頁位于http://www.example.com
,希望從跨域的服務器http://api.example.com
獲取數(shù)據(jù)。在服務器端配置允許跨域訪問的規(guī)則。具體的配置方式取決于服務器端的語言和框架。以下示例是使用 Node.js 和 Express 框架來配置CORS。
const?express?=?require('express');
const?app?=?express();
//?配置CORS中間件
app.use(function(req,?res,?next)?{
????//?允許特定域的跨域訪問
????res.header('Access-Control-Allow-Origin',?'http://www.example.com');
????//?允許發(fā)送跨域請求的HTTP方法
????res.header('Access-Control-Allow-Methods',?'GET,?POST,?PUT,?DELETE');
????//?允許的請求頭
????res.header('Access-Control-Allow-Headers',?'Content-Type');
????//?是否允許發(fā)送Cookie
????res.header('Access-Control-Allow-Credentials',?'true');
????next();
});
//?跨域請求處理
app.get('/data',?(req,?res)?=>?{
????const?data?=?{?message:?'Hello,?World!'?};
????res.send(data);
});
app.listen(80,?()?=>?{
????console.log('Server?started');
});
在這個例子中,使用了 Express 框架,并在服務器端配置了一個中間件來處理CORS。在中間件中,我們設置了允許跨域訪問的源(http://www.example.com
)、允許的HTTP方法(GET、POST、PUT、DELETE)、允許的請求頭(Content-Type)和是否允許發(fā)送Cookie。這樣就允許了來自指定域名的跨域訪問。
在客戶端的 JavaScript 代碼中,可以使用AJAX或Fetch API等方式發(fā)送跨域請求:
fetch('http://api.example.com/data',?{
????method:?'GET',
????credentials:?'include'?//?發(fā)送包含Cookie的請求
})
????.then(response?=>?response.json())
????.then(data?=>?{
????????console.log(data);?//?在控制臺中打印獲取到的數(shù)據(jù)
????})
????.catch(error?=>?{
????????console.error('Error:',?error);
????});
在這個例子中,使用了Fetch API來發(fā)送GET請求到跨域服務器的 /data
路由,并通過 credentials: 'include'
選項來發(fā)送包含Cookie的請求。然后通過 response.json()
方法解析響應數(shù)據(jù)。
通過這種方式,客戶端就能夠通過CORS允許的方式與跨域服務器進行交互,獲取數(shù)據(jù)并進行處理。例子僅供參考
代理服務器:可以設置一個代理服務器,將跨域請求轉發(fā)到目標服務器并將響應返回給客戶端。客戶端向代理服務器發(fā)出請求,代理服務器再將請求發(fā)送到目標服務器,然后將響應返回給客戶端。因為同源策略只存在于瀏覽器中,而代理服務器是在服務器端進行請求轉發(fā),所以不會受到同源策略的限制。
以下是一個使用代理服務器解決跨域問題的例子:
假設我們的前端應用運行在http://localhost:3000
,而我們想要請求的跨域API位于http://api.example.com
。
在后端設置一個代理服務器來轉發(fā)請求。以下是一個使用Node.js和Express框架實現(xiàn)的例子:
const?express?=?require('express');
const?request?=?require('request');
const?app?=?express();
//?設置代理路由
app.get('/api/data',?(req,?res)?=>?{
??const?apiUrl?=?'http://api.example.com/data';?//?目標API的URL
??//?轉發(fā)請求到目標API
??req.pipe(request(apiUrl)).pipe(res);
});
app.listen(8000,?()?=>?{
??console.log('Proxy?server?started');
});
在上述示例中,創(chuàng)建了一個代理服務器,將 /api/data
路由映射到目標 API 的 URL (http://api.example.com/data
)。通過使用 request
模塊將請求從代理服務器轉發(fā)到目標 API,并將響應返回給前端應用。
在前端應用中發(fā)起請求。假設使用 Axios 進行網(wǎng)絡請求,可以將請求發(fā)送到代理服務器的對應路由,如下所示:
import?axios?from?'axios';
axios.get('http://localhost:8000/api/data')
??.then(response?=>?{
????console.log(response.data);?//?在控制臺中打印獲取到的數(shù)據(jù)
??})
??.catch(error?=>?{
????console.error('Error:',?error);
??});
在這個例子中,向代理服務器的 /api/data
路由發(fā)送 GET 請求,代理服務器會將該請求轉發(fā)到目標 API (http://api.example.com/data
),然后將響應返回給前端應用。
WebSocket:WebSocket 是一種全雙工通信協(xié)議,它可以在客戶端和服務器之間建立持久性的連接,實現(xiàn)實時數(shù)據(jù)傳輸。由于 WebSocket 是在單個 HTTP 連接上運行的,而不受同源策略的限制,因此可以解決跨域通信的問題。
使用反向代理:在服務器端配置一個反向代理,將所有的請求轉發(fā)到目標服務器??蛻舳伺c反向代理進行通信,而反向代理與目標服務器之間是同源的,因此不會受到同源策略的限制。
以下是一個使用反向代理解決跨域問題的例子:
假設前端應用運行在 http://localhost:3000
,而想要請求的跨域 API 位于 http://api.example.com
。
在反向代理服務器上配置代理規(guī)則??梢允褂贸R姷姆聪虼矸掌鳎鏝ginx或Apache。以下是一個使用Nginx的示例配置:
server {
? ?listen 80;
? ?server_name localhost;
? ?location /api/ {
? ? ? ?proxy_pass http://api.example.com/;
? ? ? ?proxy_set_header Host $host;
? ? ? ?proxy_set_header X-Real-IP $remote_addr;
? ?}
? ?
? ?location / {
? ? ? ?proxy_pass http://localhost:3000;
? ? ? ?proxy_set_header Host $host;
? ? ? ?proxy_set_header X-Real-IP $remote_addr;
? ?}
}
在上述示例中,配置了一個Nginx服務器,監(jiān)聽在80端口,并定義了兩個代理規(guī)則。/api/
路徑下的請求將被轉發(fā)到目標 API (http://api.example.com/
),而其他所有請求將被轉發(fā)到前端應用 (http://localhost:3000
)。
啟動反向代理服務器。根據(jù)你的環(huán)境和配置方式,啟動配置好的反向代理服務器。
在前端應用中發(fā)送請求。前端應用可以直接發(fā)送請求到反向代理服務器,而不需要關心跨域問題。例如,使用Axios發(fā)送請求的示例代碼如下:
import?axios?from?'axios';
axios.get('/api/data')
??.then(response?=>?{
????console.log(response.data);?//?在控制臺中打印獲取到的數(shù)據(jù)
??})
??.catch(error?=>?{
????console.error('Error:',?error);
??});
在這個例子中,將請求發(fā)送到反向代理服務器的 /api/data
路徑,反向代理服務器會將該請求轉發(fā)到目標 API (http://api.example.com/
),然后將響應返回給前端應用。
React、Vue項目中如何解決跨域問題
在大型項目中,使用React、Vue或其他框架集成的方式,通常會有不同的方法來解決跨域問題。以下是一些常見的解決方案:
在React項目中,可以使用
http-proxy-middleware
等中間件來配置代理。以下是一個示例:
//?setupProxy.js
const?{?createProxyMiddleware?}?=?require('http-proxy-middleware');
module.exports?=?function(app)?{
??app.use(
????'/api',
????createProxyMiddleware({
??????target:?'http://api.example.com',?//?目標服務器的URL
??????changeOrigin:?true,
??????pathRewrite:?{
????????'^/api':?'',?//?可選,用于重寫路徑
??????},
????})
??);
};
在上述示例中,將所有以/api
開頭的請求代理到目標服務器http://api.example.com
??梢愿鶕?jù)實際情況進行配置。
在Vue項目中,可以在
vue.config.js
中配置代理。以下是一個示例:
//?vue.config.js
module.exports?=?{
??devServer:?{
????proxy:?{
??????'/api':?{
????????target:?'http://api.example.com',?//?目標服務器的URL
????????changeOrigin:?true,
????????pathRewrite:?{
??????????'^/api':?'',?//?可選,用于重寫路徑
????????},
??????},
????},
??},
};