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

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

一、跨域问题的本质

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

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

相关推荐
zhougl9962 小时前
html处理Base文件流
linux·前端·html
花花鱼2 小时前
node-modules-inspector 可视化node_modules
前端·javascript·vue.js
HBR666_2 小时前
marked库(高效将 Markdown 转换为 HTML 的利器)
前端·markdown
careybobo3 小时前
海康摄像头通过Web插件进行预览播放和控制
前端
TDengine (老段)4 小时前
TDengine 中的关联查询
大数据·javascript·网络·物联网·时序数据库·tdengine·iotdb
杉之5 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
喝拿铁写前端5 小时前
字段聚类,到底有什么用?——从系统混乱到结构认知的第一步
前端
再学一点就睡5 小时前
大文件上传之切片上传以及开发全流程之前端篇
前端·javascript
木木黄木木6 小时前
html5炫酷图片悬停效果实现详解
前端·html·html5
请来次降维打击!!!6 小时前
优选算法系列(5.位运算)
java·前端·c++·算法