一、什么是跨域?为什么会产生跨域?
首先,跨域的核心是浏览器的同源策略(Same-Origin Policy) 。这是一个出于安全考虑的核心机制,旨在防止不同源的网页之间进行恶意的数据交互,比如窃取用户信息、进行CSRF攻击等。
"源"(Origin)由三部分组成:协议(Protocol)、域名(Host)、端口(Port)。 只要这三者中有任何一个不同,就构成了跨域。
| URL1 | URL2 | 是否同源 | 原因 |
|---|---|---|---|
http://www.a.com/a.html |
http://www.a.com/b.html |
✅ | 协议、域名、端口都相同 |
http://www.a.com |
https://www.a.com |
❌ | 协议不同 (http vs https) |
http://www.a.com |
http://www.b.com |
❌ | 域名不同 |
http://localhost:5000 |
http://localhost:7000 |
❌ | 端口不同 |
http://www.a.com |
http://127.0.0.1:5000 |
❌ | 域名和端口都不同(即使IP相同) |
一个关键点 : 跨域限制是浏览器单方面施加的安全策略。实际上,HTTP请求已经成功发送到了服务器,服务器也返回了数据,但浏览器在接收到响应后,会检查响应头信息,如果不符合同源策略,就会拦截响应体,导致前端JS无法读取数据,从而在控制台报错。
二、如何解决跨域?
解决跨域的核心思想是:让浏览器认为这次请求是"安全"的、同源的。 主要有以下几种主流方案,按推荐程度排序:
1. CORS (Cross-Origin Resource Sharing) - 【现代、标准、首选方案】
这是W3C制定的官方标准,也是目前解决跨域的根本方案。它通过在HTTP响应头中添加一系列Access-Control-*字段,来告诉浏览器哪些源有权限访问资源。
工作原理:
-
简单请求(Simple Request) : 满足以下条件的请求,浏览器会直接发送,并在响应头中检查
Access-Control-Allow-Origin。- 请求方法为:
GET,POST,HEAD - 请求头仅包含:
Accept,Accept-Language,Content-Language,Content-Type(且值仅限application/x-www-form-urlencoded,multipart/form-data,text/plain)
- 请求方法为:
-
非简单请求(Preflighted Request) : 不满足简单请求条件的(如
PUT,DELETE方法,或Content-Type: application/json),浏览器会自动先发送一个OPTIONS 方法的预检请求(Preflight Request) 。服务器通过预检请求的响应来判断是否允许后续的真实请求。
关键响应头:
Access-Control-Allow-Origin: 必需,指定允许访问资源的源,如http://my-app.com或*(表示所有源,但不安全且无法携带Cookie)。Access-Control-Allow-Methods: 允许的HTTP方法,如GET, POST, OPTIONS。Access-Control-Allow-Headers: 允许的请求头字段。Access-Control-Allow-Credentials: 是否允许发送Cookie,若需携带cookie,后端不能使用*作为Allow-Origin的值。
实现 : 主要由后端配置。例如在Node.js (Express)中:
javascript
javascript
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'http://localhost:3000'); // 允许的源
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') {
return res.sendStatus(204); // 预检请求直接成功返回
}
next();
});
Java (Spring Boot)、PHP、Python等后端框架都有相应的CORS配置方式(如注解、过滤器、中间件)。
2. 代理服务器 (Proxy) - 【开发环境常用,生产环境可选】
利用服务器之间没有同源策略限制的特点,将前端的跨域请求变为同源请求。
-
开发环境代理 : 前端开发时,配置
webpack-dev-server或Vite的代理功能。前端请求自己的开发服务器(如/api/users),开发服务器再将请求转发到真实的后端API(如http://api.example.com/users)。-
优点: 简单快捷,无需后端配合,解决开发时的跨域问题。
-
缺点: 仅用于开发环境。
-
示例 (Vite配置):
javascriptjavascript // vite.config.js export default { server: { proxy: { '/api': { target: 'http://localhost:8080', // 后端API地址 changeOrigin: true, rewrite: (path) => path.replace(/^/api/, '') } } } }
-
-
生产环境代理: 使用Nginx等反向代理服务器。所有请求都发往Nginx,由Nginx根据规则将请求转发给后端应用服务器。
-
优点: 性能高,安全,可做负载均衡、日志记录等。
-
示例 (Nginx配置):
bashnginx location /api { proxy_pass http://api.example.com; # 转发到真实API服务器 # 以下CORS头也可在Nginx层配置,作为备选方案 add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; }
-
3. JSONP (JSON with Padding) - 【古老、仅作了解】
利用<script>标签的src属性不受同源策略限制的"漏洞"。
-
原理: 前端动态创建一个
<script>标签,其src指向后端API,并带上一个回调函数名(如?callback=handleData)。后端将数据作为参数拼接到该函数调用中返回(如handleData({...}))。前端预先定义好handleData函数,数据返回后即可执行。 -
缺点:
- 只支持
GET请求。 - 安全性差,容易遭受XSS攻击。
- 错误处理不便。
- 只支持
-
现状: 在现代项目中已基本被CORS取代,仅在需要兼容非常古老的浏览器时才可能用到。
4. 其他方案(特定场景)
window.postMessage: 用于跨域iframe之间的通信。- WebSocket: 协议本身不受同源策略限制,但建立连接的握手过程仍需遵循HTTP的同源策略。一旦连接建立,后续通信是自由的。
document.domain: 仅适用于主域相同、子域不同的情况(如a.b.com和c.b.com),通过设置document.domain = 'b.com'来实现。
三、总结与选择
在实际项目中,我的选择策略是:
- 首选CORS:这是最标准、最灵活的方案。前后端分离项目中,由后端配置CORS策略是最佳实践。
- 开发环境用代理:为了提升开发效率,避免频繁依赖后端,前端配置开发服务器代理是必须的。
- 生产环境可用Nginx代理:对于已部署的项目,如果不想修改后端代码,可以在Nginx层做反向代理和CORS配置。
- 基本不用JSONP:除非有特殊的历史遗留项目或兼容性要求。
四、常见问题排查(加分项)
如果遇到CORS报错,我会按以下步骤排查:
- 看网络请求 :在浏览器开发者工具的Network面板,检查请求的
Origin和响应的Access-Control-Allow-Origin是否匹配。 - 看预检请求 :
OPTIONS请求是否成功(状态码200/204)?如果失败,可能是服务器端路由或安全策略(如防火墙、WAF)拦截了OPTIONS请求。 - 看响应头 :
Access-Control-Allow-Origin是否为*或正确的源?Access-Control-Allow-Methods是否包含了请求的方法? - 看凭证 :如果请求需要携带Cookie,后端是否设置了
Access-Control-Allow-Credentials: true,并且Access-Control-Allow-Origin不能为*? - 检查重复配置 :响应头中
Access-Control-Allow-Origin是否出现了多次?这通常是由于服务器和应用框架(如Spring Security)同时配置了CORS导致的,需要统一管理。