前端跨域问题及解决方案

1. 为什么会出现跨域问题?

跨域(Cross-Origin)是指在浏览器中,出于安全考虑,网页的 JavaScript 脚本只能访问与当前页面同源的资源。同源策略(Same-Origin Policy)规定,只有协议、域名和端口都相同的请求才会被允许,否则会被浏览器阻止,这就是所谓的“跨域”问题。比如,当前网站是 https://www.wyxup.top JavaScript 向 http://api.wyxup.top 发送请求时,由于不同的域名或端口,浏览器会认为这是跨域请求。

1.1 同源策略

同源策略要求以下三个部分必须相同:

  • 协议(Protocol):如 httphttps
  • 域名(Domain):如 wyxup.top
  • 端口(Port):如 80443

如果三者中有任何一个不同,就会触发跨域问题。

1.2 跨域示例

以下是一些跨域的示例:


2. 跨域问题的错误代码

  • CORS 错误:Access to XMLHttpRequest at ‘http://wyxup.top‘ from origin ‘http://localhost:4000‘ has been blocked by CORS policy。
  • JSONP 错误:Uncaught SyntaxError: Unexpected token <。

3. 跨域解决方案

以下是常见的跨域解决方案:

3.1 CORS(跨域资源共享)

CORS(Cross-Origin Resource Sharing)是一种 W3C 标准,它允许浏览器向跨域服务器发送请求,并允许服务器通过设置 HTTP 响应头部来告知浏览器允许跨域请求。CORS 的核心机制是服务器通过响应头 Access-Control-Allow-Origin 指定允许访问的域名,浏览器根据该响应头来决定是否允许请求。

3.1.1 常见的 CORS 响应头

  • Access-Control-Allow-Origin:指定允许的源(如 * 表示允许所有域,或者指定具体的域名)。
  • Access-Control-Allow-Methods:允许的请求方法(如 GET, POST, PUT 等)。
  • Access-Control-Allow-Headers:允许客户端携带的头部字段。
  • Access-Control-Allow-Credentials:是否允许带有凭证(如 Cookies)的请求。

3.1.2 如何实现 CORS

  • 服务器端需要设置响应头以允许跨域请求
  • 如果是简单请求,浏览器会直接发送请求
  • 对于复杂请求(如 PUTDELETE,或者自定义请求头等),浏览器会先发送一个 OPTIONS 预检请求,服务器需要响应以下头:
1
2
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
1
2
3
4
5
6
7
8
9
10
fetch('https://api.wyxup.top/data', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include' // 带上 Cookies
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
1
2
3
4
5
// 服务器端响应头设置示例(以 Node.js 为例)
response.setHeader('Access-Control-Allow-Origin', '*'); // 或指定具体域名
response.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
response.setHeader('Access-Control-Allow-Headers', 'Content-Type');
response.setHeader('Access-Control-Allow-Credentials', 'true');

3.2 JSONP(JSON with Padding)

JSONP 是一种通过 <script> 标签来绕过同源策略的老旧方案。它将跨域请求转化为对 JavaScript 代码的请求。通过服务器返回一段 JavaScript 代码,且这段代码执行时会调用一个回调函数,回调函数中包含需要的结果数据。

3.2.1 实现原理

  • 前端通过 script 标签发送跨域请求,后端返回一个带有回调函数的 JavaScript 脚本。
  • 前端回调该函数,并处理返回的数据。

3.2.2 示例代码

前端代码:

1
2
3
4
5
6
7
function handleResponse(data) {
console.log(data);
}

const script = document.createElement('script');
script.src = 'http://wyxup.top/api?callback=handleResponse';
document.body.appendChild(script);

服务器端代码(Node.js):

1
2
3
4
5
6
7
const http = require('http');

http.createServer((req, res) => {
const callback = req.url.split('callback=')[1];
const data = JSON.stringify({ message: 'Hello, JSONP!' });
res.end(`${callback}(${data})`);
}).listen(3000);

3.2.3 局限性

  • 仅支持GET请求。
  • 安全性较低,容易受到 XSS 攻击。

3.3 代理(Proxy)

代理的做法是通过设置一个中间层(例如后端服务器或开发环境中的代理服务器)来转发请求,绕过浏览器的跨域限制。通过代理,前端请求实际上是发送到同源的服务器,服务器再将请求转发到跨域的目标服务器。

3.3.1 实现原理

  • 前端请求同源服务器,同源服务器转发请求到目标服务器。
  • 目标服务器返回数据,同源服务器再将数据返回给前端。

3.3.2 示例代码

使用 Node.js 实现代理服务器:

1
2
3
4
5
6
7
8
const http = require('http');
const httpProxy = require('http-proxy');

const proxy = httpProxy.createProxyServer({});

http.createServer((req, res) => {
proxy.web(req, res, { target: 'http://wyxup.top' });
}).listen(3000);

前端代码:

1
2
3
4
5
6
7
8
9
10
11
12
// webpack.config.js 配置代理
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'https://api.wyxup.top',
changeOrigin: true,
pathRewrite: { '^/api': '' }
}
}
}
};

3.4 Nginx 反向代理

可以在后端服务器设置一个代理,将前端的请求转发给目标服务器,从而避免跨域问题。

3.4.1 配置示例

在 Nginx 配置文件中添加以下内容:

1
2
3
4
5
6
7
8
9
server {
listen 80;
server_name localhost;

location /api {
proxy_pass http://wyxup.top;
add_header Access-Control-Allow-Origin *;
}
}

前端直接请求 Nginx 服务器


3.5 WebSocket

WebSocket 作为一种长连接技术,不受同源策略的限制,因此可以用于跨域通信。前端和服务器之间建立 WebSocket 连接后,可以进行实时双向数据传输,适用于实时应用场景。

3.5.1 示例代码

前端代码:

1
2
3
4
5
6
7
const socket = new WebSocket('ws://wyxup.top');

socket.onmessage = event => {
console.log(event.data);
};

socket.send('Hello, WebSocket!');

服务器端代码(Node.js):

1
2
3
4
5
6
7
8
9
10
const WebSocket = require('ws');

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

wss.on('connection', ws => {
ws.on('message', message => {
console.log(`收到消息: ${message}`);
ws.send('Hello, Client!');
});
});