跨域问题全解析:七种方法轻松拿捏跨域

跨域问题全解析:原理、解决方案与最佳实践

一、跨域问题的本质

1. 同源策略(Same Origin Policy)

  • 浏览器的安全机制,限制不同源的资源交互

  • 同源定义:协议、域名、端口完全相同

  • 受限制的操作:

    javascript 复制代码
    fetch() // AJAX请求
    DOM访问 // iframe内容
    localStorage // 本地存储

2. 跨域场景示例

javascript 复制代码
// 典型跨域请求
fetch('https://api.example.com/data');

3. 浏览器控制台报错

plaintext 复制代码
Access to fetch at 'https://api.example.com/data' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

二、七种跨域解决方案详解

1. JSONP(JSON with Padding)

原理 :借助<script>标签的src属性不受同源策略限制的特性。客户端在页面里动态创建<script>标签,向服务器请求一个 JSON 数据,并在请求的 URL 中带上一个回调函数名作为参数。服务器收到请求后,会把 JSON 数据包装在回调函数中返回给客户端。客户端的<script>标签加载这个响应,就会执行回调函数,从而获取到服务器返回的 JSON 数据。

示例代码

服务端(Node.js + Express):

javascript 复制代码
const express = require('express');
const app = express();

app.get('/jsonp', (req, res) => {
    const callback = req.query.callback;
    const data = { message: 'JSONP response' };
    const jsonp = `${callback}(${JSON.stringify(data)})`;
    res.send(jsonp);
});

const port = 3001;
app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

客户端:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>JSONP Example</title>
</head>
<body>
    <script>
        function handleResponse(data) {
            console.log(data.message);
        }
    </script>
    <script src="http://localhost:3001/jsonp?callback=handleResponse"></script>
</body>
</html>
  • 优点:兼容性好,能在老版本浏览器中使用;实现简单,服务端改动小。

  • 缺点:仅支持 GET 请求;安全性欠佳,易遭受 XSS 攻击;调试不便。

  • 适用场景:适用于简单的数据请求,尤其是对兼容性要求较高的场景。

2. CORS(Cross-Origin Resource Sharing)

原理:这是一种现代的跨域解决方案,由 W3C 制定。它允许服务器在响应头里设置一些特定字段,告知浏览器哪些跨域请求是被允许的。浏览器会根据这些响应头信息来决定是否允许跨域请求。

示例代码

服务端(Node.js + Express) :

javascript 复制代码
const express = require('express');
const app = express();

app.use((req, res, next) => {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
    next();
});

app.get('/data', (req, res) => {
    res.json({ message: 'CORS response' });
});

const port = 3001;
app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

客户端:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>CORS Example</title>
</head>
<body>
    <script>
        fetch('http://localhost:3001/data')
           .then(response => response.json())
           .then(data => console.log(data.message));
    </script>
</body>
</html>
  • 优点:支持所有 HTTP 请求方法;是 W3C 标准,被现代浏览器广泛支持;安全性较高,可通过服务器精确控制跨域访问。

  • 缺点:需要服务器端进行配置;对于复杂请求,浏览器会先发送预请求(OPTIONS 请求),增加了请求次数。

  • 适用场景:适用于前后端分离开发,尤其是使用现代浏览器的项目。

3. Nginx 反向代理

原理:Nginx 是一款高性能的 HTTP 服务器和反向代理服务器。反向代理是指服务器接收客户端的请求,然后将请求转发到其他服务器,并将目标服务器的响应返回给客户端。在跨域场景中,客户端向同源的 Nginx 服务器发送请求,Nginx 服务器将请求转发到目标服务器,再把目标服务器的响应返回给客户端,这样就绕过了浏览器的同源策略。

示例代码

Nginx 配置:

nginx 复制代码
server {
    listen 80;
    server_name yourdomain.com;

    location /api {
        proxy_pass http://localhost:3001;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

客户端:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Nginx Proxy Example</title>
</head>
<body>
    <script>
        fetch('/api/data')
           .then(response => response.json())
           .then(data => console.log(data.message));
    </script>
</body>
</html>

优点 :对前端代码无侵入性,前端开发人员无需关心跨域问题;可在服务器端进行统一配置和管理。 缺点:需要额外部署 Nginx 服务器;配置相对复杂,需要了解 Nginx 的相关知识。

适用场景:适用于企业级项目,尤其是对安全性和性能要求较高的场景。

4. Node 中间件代理

原理:利用 Node.js 搭建一个中间服务器,客户端向这个中间服务器发送请求,中间服务器再将请求转发到目标服务器,并把目标服务器的响应返回给客户端。由于客户端和中间服务器是同源的,所以不会受到浏览器同源策略的限制。

示例代码

服务端(Node.js + Express + http - proxy - middleware) :

javascript 复制代码
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');

const app = express();

app.use('/api', createProxyMiddleware({
    target: 'http://localhost:3001',
    changeOrigin: true,
    pathRewrite: { '^/api': '' }
}));

const port = 3000;
app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

客户端:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Node Proxy Example</title>
</head>
<body>
    <script>
        fetch('/api/data')
           .then(response => response.json())
           .then(data => console.log(data.message));
    </script>
</body>
</html>

优点:易于集成到前端项目中,开发和调试方便;可灵活控制请求和响应。

缺点:需要额外的 Node.js 服务器;性能可能不如 Nginx 反向代理。

适用场景:适用于前端开发环境,尤其是在开发过程中需要快速解决跨域问题的场景。

5. WebSocket

原理:WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,它不受同源策略的限制。客户端和服务器通过 WebSocket 协议建立连接后,就可以在连接上实时地发送和接收数据。

示例代码: 服务端(Node.js + ws) :

javascript 复制代码
const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 3001 });

wss.on('connection', (ws) => {
    ws.on('message', (message) => {
        console.log(`Received: ${message}`);
        ws.send('WebSocket response');
    });
});

客户端:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket Example</title>
</head>
<body>
    <script>
        const socket = new WebSocket('ws://localhost:3001');

        socket.addEventListener('open', () => {
            socket.send('Hello, server!');
        });

        socket.addEventListener('message', (event) => {
            console.log(`Received from server: ${event.data}`);
        });
    </script>
</body>
</html>

优点:支持全双工通信,实时性强;不受同源策略限制;性能较高。

缺点:协议与 HTTP 不同,需要服务器和客户端都支持 WebSocket 协议;对于简单的数据请求,使用 WebSocket 会增加开销。

适用场景:适用于实时通信场景,如聊天应用、实时数据推送等。

6. postMessage

原理postMessage是 HTML5 提供的一个跨窗口通信的 API,可用于在不同窗口(如父窗口与子窗口、不同源的 iframe 之间)之间传递消息。通过postMessage方法,一个窗口可以向另一个窗口发送消息,接收方窗口可以监听message事件来获取消息。

示例代码

父页面:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Parent Page</title>
</head>
<body>
    <iframe id="childFrame" src="http://localhost:3002/child.html"></iframe>
    <script>
        const childFrame = document.getElementById('childFrame');
        childFrame.contentWindow.postMessage('Hello from parent', 'http://localhost:3002');

        window.addEventListener('message', (event) => {
            if (event.origin === 'http://localhost:3002') {
                console.log(`Received from child: ${event.data}`);
            }
        });
    </script>
</body>
</html>

子页面(child.html) :

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Child Page</title>
</head>
<body>
    <script>
        window.addEventListener('message', (event) => {
            if (event.origin === 'http://localhost:3000') {
                console.log(`Received from parent: ${event.data}`);
                event.source.postMessage('Hello from child', event.origin);
            }
        });
    </script>
</body>
</html>

优点:安全可靠,可通过指定目标源来控制消息的接收方;适用于不同源的窗口之间的通信。

缺点:只能用于窗口之间的通信,不能用于普通的 HTTP 请求;消息传递是异步的,处理起来相对复杂。

适用场景:适用于在不同源的窗口(如父窗口与 iframe)之间进行数据交互的场景。

7. document.domain

原理 :该方法只适用于主域名相同但子域名不同的情况。通过将父窗口和子窗口的document.domain属性设置为相同的主域名,就可以实现这两个窗口之间的跨域通信。不过,现代浏览器(如谷歌浏览器)已逐渐禁止使用该方法,因为它存在一定的安全风险。

示例代码

主域名 a.example.com 页面:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>a.example.com</title>
    <script>
        document.domain = 'example.com';
    </script>
</head>
<body>
    <iframe src="http://b.example.com"></iframe>
</body>
</html>

子域名 b.example.com 页面:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>b.example.com</title>
    <script>
        document.domain = 'example.com';
    </script>
</head>
<body>
    <script>
        // 可以访问父页面的一些属性和方法
        console.log(parent.someVariable);
    </script>
</body>
</html>

优点 :实现简单,只需设置document.domain属性。

缺点:适用范围窄,仅适用于主域名相同的情况;安全性低,易遭受攻击;现代浏览器支持度低。

适用场景:由于安全性和兼容性问题,不建议在实际项目中使用该方法。

三、核心解决方案对比

方案 核心原理 优点 缺点 典型场景 复杂度 兼容性 安全性
JSONP script 标签跨域加载 兼容性好,简单易用 仅 GET,XSS 风险 简单数据请求
CORS 服务端响应头控制 支持全方法,标准方案 需服务端配置,预请求 前后端分离项目 现代
反向代理 中间服务器转发请求 前端透明,支持复杂请求 需要额外服务器 企业级项目
WebSocket 独立协议长连接 实时双向通信 协议不同,需单独实现 聊天 / 推送 现代
postMessage 窗口间通信 API 安全可控 仅限窗口通信 iframe 交互 现代
Node 代理 开发环境中间件转发 开发调试方便 生产环境不适用 前端开发阶段 现代
document.domain 主域名设置 历史方案 受浏览器限制,不安全 已废弃

四、总结

不同的跨域解决方案各有优缺点,在实际开发中,要依据具体的业务需求和场景来选择合适的方案。

一般来说,CORS 是现代 Web 开发中常用的跨域解决方案,它支持所有 HTTP 请求方法,安全性高且被广泛支持。

而 JSONP 则适用于对兼容性要求较高的简单数据请求场景。

对于前后端分离开发,可考虑使用 Nginx 反向代理或 Node 中间件代理。

实时通信场景可选用 WebSocket,不同窗口之间的通信则可使用postMessage

通过合理选择和组合跨域方案,可以在保证安全性的前提下,实现高效的跨域资源交互。

相关推荐
Goboy7 分钟前
深入理解Java的文件写入与资源管理
后端·面试·架构
yinzhiqing8 分钟前
ubuntu24设置拼音输入法,解决chrome不能输入中文
前端·数据库·chrome
知识分享小能手8 分钟前
JavaScript学习教程,从入门到精通,XMLHttpRequest 与 Ajax 请求详解(25)
开发语言·javascript·学习·ajax·前端框架·css3·html5
_一条咸鱼_11 分钟前
Android大厂面试通关秘籍
android·面试·android jetpack
PythonPioneer15 分钟前
每日Html 4.24
前端·html
小小小小宇17 分钟前
H5秒开且不影响版本更新
前端
我有一棵树21 分钟前
元素滚动和内容垂直居中同时存在,完美的 html 元素垂直居中的方法flex + margin: auto
前端·html
excel40 分钟前
webpack 运行时模版 第 四 节 /lib/RuntimeTemplate.js
前端
好_快43 分钟前
Lodash源码阅读-createSet
前端·javascript·源码阅读
好_快1 小时前
Lodash源码阅读-remove
前端·javascript·源码阅读