跨域是指從一個域名的網頁去請求另一個域名的資源。比如,從www.a.com
域名的網頁去請求www.b.com
域名的資源,只要協議、域名、端口有任何一個不同,都被當作是不同的域,跨域問題通常由瀏覽器的同源策略引起的。
同源策略就是瀏覽器出于安全考慮而制定的,例如數據安全,服務器安全,減少 xss(跨站腳本攻擊),CSRF(跨站請求偽造) 等攻擊。所謂同源,就是協議,域名,端口號都相同才能請求數據。如果是非同源請求發送后,瀏覽器會攔截響應。
1. 瀏覽器出于安全考慮(數據安全,服務器安全,減少 xss,CSRF 攻擊)
2. https:
協議 子域名 主域名 端口號 路徑
3. 非同源,請求發送后,瀏覽器會攔截響應
跨域解決方案
1.jsonp
- 借助 script 標簽 src 屬性不受同源策略的限制(經常要加載第三方庫等),來發送請求
- 后端將數據作為 callback 函數的實參,返回給前端一個 callback 的調用形式
- 瀏覽器接收到 callback 的調用會自動執行全局的callback函數
// 前端
<button onclick="handle()">請求</button>
<script>
function jsonp(url, cb) {
return new Promise((resolve, reject) => {
const script = document.createElement('script')
window[cb] = function (data) {
// console.log(data) // 后端返回的數據
resolve(data)
}
script.src = `${url}?cb=${cb}`
// 將標簽加到document.body時,瀏覽器就會發url請求
document.body.appendChild(script)
// callback('hello world')
})
}
function handle() {
jsonp('http://localhost:3000', 'callback').then(res => {
console.log(res)
})
}
</script>
前端通過一個jsonp函數,傳入請求的url和回調函數的名稱cb,然后在jsonp函數里面創建一個script標簽,將該標簽的src屬性變為url后面用?拼接傳入的字符串。// 后端
const http = require('http');
http.createServer((req, res) => {
// 獲取前端傳過來參數
const query = new URL(req.url, `http://${req.headers.host}`).searchParams
if (query.get('cb')) {
const cb = query.get('cb') // 'callback'
const data = 'hello world'
const result = `${cb}("${data}")` // "callback('hello world')"
res.end(result)
}
}).listen(3000);
后端創建一個http服務器,解析前端傳過來的callback
字符串參數,將數據作為參數返回"callback('hello world')"
格式的一個函數。
缺點:
2. CORS(跨資源共享)
服務器在響應頭中后端設置 Access-Control-Allow-Origin: '域名白名單',告訴瀏覽器允許哪個源進行跨域訪問。可以是具體的域名,也可以是*
表示允許所有源。如下
const http = require('http')
const server = http.createServer((req, res) => {
res.writeHead(200, {
'Access-Control-Allow-Origin': '*',
// 也可自行設置指定的域名可以訪問
// 例如: 'Access-Control-Allow-Origin': 'http://192.168.2.1:5500'
})
res.end('hello world');
})
server.listen(3000)
3.nginx反向代理
前端服務器和后端服務器不在同一個域名下, 前端服務器通過nginx 反向代理來訪問后端服務器。
服務器和服務器之間的通信不存在跨域,可以開一臺中間服務器(nginx
),后端無需改變。前端把請求發給nginx
, nginx 服務器把請求轉發給后端的服務器,后端的服務器響應給 nginx
服務器,nginx
服務器加上響應頭以后,再返回給前端,如下;
4. node 中間件代理
原理同nginx 反向代理,只不過多寫一個node后端,前端服務器和后端服務器不在同一個域名下,前端服務器通過 node 中間件來訪問后端服務器。
5. websocket
- 傳統的前后端通信是基于http協議的,是單向的,只能從一端發到另一端,無法雙向通信
- websocket 是基于tcp協議的,是雙向的,可以從一端發送到另一端,也可以從另一端發送到一端
- socket協議一旦建立連接,就可以一直保持通信狀態,不需要每次都建立連接,但是會更開銷性能
// 前端
<script>
function WebSocketTest(url, params = {}) {
return new Promise((resolve, reject) => {
const socket = new WebSocket(url)
// 當連接打開時發送數據
socket.onopen = () => {
socket.send(JSON.stringify(params))
}
// 接收到后端的消息時打印數據并解決Promise
socket.onmessage = (event) => {
console.log(event.data)
resolve(event.data)
}
})
}
WebSocketTest('ws://localhost:3000', { age: 18 }).then(res => {
console.log(res)
})
</script>
// 后端
const WebSocket = require('ws');
// 在 3000 端口上建立 WebSocket 伺服器 (隨時都在線的服務)
const ws = new WebSocket.Server({ port: 3000 });
let count = 0
// 監聽連接
ws.on('connection', (obj) => {
// console.log(obj);
obj.on('message', (msg) => { // 收到客戶端發來的消息
// console.log(msg.toString()); // 客戶端傳過來的數據
obj.send('收到了')
setInterval(() => {
count++
obj.send(count)
}, 2000)
})
})
6. postMessage
當父級頁面和iframe頁面不在同一個域名下,他們之間的數據傳輸也存在跨域問題,父級頁面和iframe頁面之間可以通過posMessage來通信。
<h2>首頁</h2>
<iframe id="frame" src="http://127.0.0.1:5500/%E8%B7%A8%E5%9F%9F/postMessage/detail.html"
frameborder="0" width="800" height="500"></iframe>
<script>
let obj = { name: '阿杰', age: 18 }
document.getElementById('frame').onload = function () {
this.contentWindow.postMessage(obj, 'http://127.0.0.1:5500')
window.onmessage = function (e) { // 接收iframe發送的消息
console.log(e.data);
}
}
</script>
<h3>詳情頁 --- <span id="title"></span></h3>
<script>
const title = document.getElementById("title");
window.onmessage = function (e) {
// console.log(e.data);
title.innerText = e.data.age;
e.source.postMessage("阿杰 20了", e.origin) // 向父級頁面發送消息
}
</script>
7.document.domain
通過設置document.domain
來允許同一主域名下的跨域通信,原理同postMessage,但是谷歌禁止了這種方法
總結
跨域:是指從一個域名的網頁去請求另一個域名的資源。只要協議、域名、端口有任何一個不同,都被當作是不同的域。跨域問題通常由瀏覽器的同源策略引起,同源策略是為了保證用戶信息的安全,防止惡意網站竊取數據
同源策略:瀏覽器出于安全考慮(數據安全,服務器安全,減少 xss,CSRF 攻擊)而制定的一種只有協議、域名、端口號都相同才能請求數據的規定,非同源請求發送后,瀏覽器會攔截響應。
跨域方案:
- jsonp(script標簽的src屬性不受同源策略的限制)
- CORS(跨資源共享,通知瀏覽器哪些域名可以訪問)
- websocket(socket協議可以保持長時間的連接,不受同源的限制,天生可以跨域)
- postMessage(父級頁面和iframe頁面之間可以通過posMessage來通信)
- document.domain(通過設置來允許同一主域名下的跨域通信,谷歌禁止了)
常見的解決方案有:CORS適用于需要支持多種HTTP方法(如GET、POST、PUT等)的現代Web應用,nginx 反向代理適用于前后端分離的項目,可以在服務器層面統一處理跨域問題。
該文章在 2025/2/21 16:01:37 編輯過