如今的 Web 项目大多采用前后端分离架构,这使得跨域问题变得常见。

CORS(Cross-Origin Resource Sharing,跨域资源共享)是浏览器的安全机制。当前端(如 http://localhost:3000)请求后端(如 http://localhost:5000)时,因为域名/端口不同,浏览器会阻止请求,除非后端明确允许。

一、什么是跨域问题

同源策略

同源策略要求 协议主机端口 完全相同才可以互相访问。只要有一个不同,就不能访问。

注意:跨域限制仅在浏览器中生效。

如果浏览器访问 https://www.helloworld.net/special,在此页面中可以请求接口 https://www.helloworld.net/getSpecialList,因为它们的协议、主机和端口都相同,所以可以请求成功。否则,不可以访问。

img

跨域问题的现象

如果出现跨域问题,浏览器控制台会提示:

1
has been blocked by CORS policy: Response to preflight request does not pass access control check

翻译过来就是:已被 CORS 策略阻止:对预检请求的响应未通过访问控制检查

例如:

  • 前端:浏览器正在访问 https://www.helloworld.net/special
  • 后端:请求接口 https://tiger-api.helloworld.net/v1/special/getSpecialList

因为域名不同,如果没有配置相关的跨域参数,浏览器会拦截响应,导致请求失败。

img

二、CORS 工作原理

浏览器拦截了响应,如果在响应头中添加一些特殊字段,浏览器看到这些字段后就不再拦截,从而解决跨域问题。

核心响应头

后端在响应时需要添加以下字段:

响应头 说明
Access-Control-Allow-Origin 必须。值为请求的 Origin*(接受任意域名)
Access-Control-Allow-Credentials 可选。布尔值,是否允许发送 Cookie。设为 true 表示允许携带凭证
Access-Control-Allow-Methods 必须。逗号分隔的字符串,服务器支持的所有跨域请求方法
Access-Control-Allow-Headers 可选。允许的自定义请求头
Access-Control-Expose-Headers 可选。前端可访问的额外响应头
Access-Control-Max-Age 可选。预检请求缓存时间(秒)

其中最重要的是 Access-Control-Allow-Origin,设置为 * 允许所有域访问。

三、解决方案

方案一:后端配置 CORS(推荐)🔥

最根本的解决方案是在后端添加 CORS 配置。

1. 环境变量配置(.env)

1
2
3
4
5
# 开发环境
ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000

# 生产环境
ALLOWED_ORIGINS=https://yourdomain.com,https://www.yourdomain.com

2. Node.js 中间件配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
const cors = require('cors');

function getAllowedOrigins() {
const origins = process.env.ALLOWED_ORIGINS || 'http://localhost:3000';
return origins.split(',').map(origin => origin.trim());
}

app.use(cors({
// 动态验证请求来源
origin: (origin, callback) => {
const allowedOrigins = getAllowedOrigins();

// 允许无 origin 的请求(Postman、curl、服务端请求)
if (!origin) {
return callback(null, true);
}

// 检查来源是否在白名单中
if (allowedOrigins.includes(origin)) {
callback(null, true);
} else {
console.warn('⚠️ CORS 拒绝访问');
console.warn(' 请求来源:', origin);
console.warn(' 允许来源:', allowedOrigins.join(', '));
callback(new Error('不允许的 CORS 来源'));
}
},

// 允许携带认证信息
credentials: true,

// 允许的 HTTP 方法
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'],

// 允许的请求头
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'Accept'],

// 暴露的响应头
exposedHeaders: ['Content-Range', 'X-Content-Range'],

// 预检请求缓存时间(秒)
maxAge: 86400 // 24小时
}));

3. 配置项说明

配置项 说明 常用值
origin 允许的来源 函数动态判断或字符串
credentials 是否允许携带凭证 true/false
methods 允许的 HTTP 方法 ['GET', 'POST', ...]
allowedHeaders 允许的请求头 ['Content-Type', ...]
exposedHeaders 前端可访问的响应头 ['X-Total-Count']
maxAge 预检缓存时间(秒) 86400

4. 前端配合配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// fetch
fetch('http://localhost:5000/api/data', {
method: 'POST',
credentials: 'include', // 携带 cookies
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ data: 'value' })
});

// axios
axios.get('http://localhost:5000/api/data', {
withCredentials: true // 携带 cookies
});

方案二:开发环境代理(推荐)🔥

如果无法修改后端 CORS 配置,可以在开发环境配置代理。

Vite 项目

1
2
3
4
5
6
7
8
9
10
11
12
// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'https://api.example.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
}

Webpack / Create React App

1
2
3
4
// package.json
{
"proxy": "https://api.example.com"
}

方案三:Nginx 反向代理(生产环境)

在生产环境中,使用 Nginx 将前端和后端放在同一域名下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
listen 80;
server_name yourdomain.com;

# 前端静态资源
location / {
root /usr/share/nginx/html;
index index.html;
}

# 后端 API 代理
location /api/ {
proxy_pass https://api.example.com/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}

方案四:搭建自己的代理服务器

使用 Node.js 创建简单的代理:

1
2
3
4
5
6
7
8
9
10
11
12
13
// proxy-server.js
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');

const app = express();

app.use('/api', createProxyMiddleware({
target: 'https://api.example.com',
changeOrigin: true,
pathRewrite: { '^/api': '' }
}));

app.listen(3000);

方案五:CORS 代理服务(仅开发测试)

利用第三方代理服务:

1
2
3
4
5
6
const proxyUrl = 'https://cors-anywhere.herokuapp.com/';
const targetUrl = 'https://api.example.com/data';

fetch(proxyUrl + targetUrl)
.then(response => response.json())
.then(data => console.log(data));

⚠️ 不建议在生产环境使用公共代理,存在安全和稳定性风险。

方案六:JSONP(仅支持 GET 请求)

如果后端支持 JSONP:

1
2
3
4
5
6
7
8
9
10
11
function jsonp(url, callback) {
const script = document.createElement('script');
script.src = `${url}?callback=${callback}`;
document.body.appendChild(script);
}

window.handleData = function(data) {
console.log(data);
};

jsonp('https://api.example.com/data', 'handleData');

方案七:浏览器插件(仅开发调试)

安装浏览器扩展如 “Allow CORS” 临时禁用跨域限制,但这只在本地有效。

四、生产环境建议

对于生产环境,最佳方案是:

  1. 后端配置 CORS:最根本的解决方案
  2. Nginx 反向代理:将前后端放在同一域名下
  3. 云函数/Serverless:作为中间层转发请求

代理方式的本质是把跨域问题转移到服务器端,因为服务器之间的通信不受同源策略限制。

五、常见问题

Q:localhost 和 127.0.0.1 需要分别配置吗?

A: 是的,浏览器认为它们是不同的源,都需要添加到白名单。

Q:为什么 Postman 能访问但浏览器不行?

A: Postman 不受同源策略限制,浏览器会强制执行 CORS 检查。

Q:配置了 CORS 但还是报错?

A: 检查以下几点:

  1. 前端请求是否包含 credentials: 'include'withCredentials: true
  2. 后端是否设置 credentials: true
  3. Origin 是否完全匹配(包括协议、端口)
  4. 是否正确处理 OPTIONS 预检请求

六、总结

  • 同源策略:协议、主机、端口三者都相同才是同一个源,只有同源的资源才能互相访问
  • 跨域问题本质:浏览器的同源策略造成,浏览器拦截了跨域响应
  • 解决思路:在响应头添加相应字段,或使用代理将请求转到同源服务器
  • 最佳实践:后端配置 CORS + 生产环境使用 Nginx 反向代理