一、什么是"跨域"?
在前端开发中,我们经常听到一个词:"跨域"。那到底什么是跨域呢?我们先从一个生活中的例子说起。
假设你住在一栋公寓楼里,每天都要去隔壁栋的超市买东西。但有一天,物业突然说:"本楼住户只能使用本楼内的设施。"于是你就不能再去隔壁栋的超市了------这就是一种"限制访问"的行为。
在网页世界中,"跨域"其实就是浏览器为了安全考虑,限制网页访问不同域名(或端口、协议)下的资源的一种机制。
1.1 什么情况下会触发跨域?
要判断是否是跨域,只需要记住一句话:
只要请求的URL与当前页面的协议、域名、端口有一个不一致,就会触发跨域。
举个例子:
当前页面地址 | 请求的目标地址 | 是否跨域 | 原因 |
---|---|---|---|
a.com/index.html | a.com/data.json | 否 | 协议、域名、端口都相同 |
a.com/index.html | a.com/data.json | 是 | 协议不同(http vs https) |
a.com:80/index.html | a.com:8080/data.json | 是 | 端口不同(80 vs 8080) |
a.com/index.html | b.com/data.json | 是 | 域名不同(a.com vs b.com) |
二、为什么会有跨域问题?
跨域问题的核心目的是为了保护用户的安全。
比如你在银行网站登录了账户,然后又打开一个恶意网站,如果这个网站可以直接访问银行网站的数据,那么你的账号信息就可能被盗用。因此,浏览器设置了跨域限制,防止这种"跨站攻击"。
三、常见的跨域场景有哪些?
跨域最常见于前后端分离开发中。比如:
- 前端运行在
http://localhost:3000
- 后端接口部署在
http://api.example.com
这时候前端调用后端接口时,就会遇到跨域问题。
再比如,你想通过 JavaScript 获取某个第三方 API 的数据,也可能因为跨域被拦截。
四、如何解决跨域问题?
下面介绍几种常见的解决方案,我们会从易到难逐一讲解,并尽量用通俗的语言说明原理。
4.1 使用 CORS(推荐)
CORS(Cross-Origin Resource Sharing),中文叫"跨域资源共享",是最标准、最推荐的方式。
4.1.1 原理简述:
CORS 的核心思想是:服务器告诉浏览器,"我允许某些特定来源访问我的资源"。
这就像物业说:"虽然原则上不允许外来人员进入小区,但我认识隔壁栋的小王,他可以进来。"
4.1.2 如何配置?
服务器端需要设置几个 HTTP 头信息,最常见的有:
http
Access-Control-Allow-Origin: *
或者指定允许的域名:
http
Access-Control-Allow-Origin: http://your-frontend-domain.com
这样浏览器就知道:"哦,这个接口允许我访问,没问题。"
4.1.3 案例说明:
假设你写了一个前端项目,在本地运行在 http://localhost:3000
,你要访问的后端接口是 http://api.example.com/login
。
如果你发现浏览器控制台报错:
csharp
Blocked by CORS policy: No 'Access-Control-Allow-Origin' header present on the requested resource.
这就说明后端没有正确设置响应头,你需要让后端加上:
http
Access-Control-Allow-Origin: http://localhost:3000
或者更宽松一点:
http
Access-Control-Allow-Origin: *
⚠️ 注意:
*
表示允许所有来源访问,但在生产环境中建议指定具体的域名以增强安全性。
4.2 JSONP(过时但曾经流行)
JSONP 是一种比较老的技术,现在基本已经被 CORS 替代,但在一些老旧系统中还能看到它的身影。
4.2.1 原理简述:
我们知道 <script>
标签是可以跨域加载 JS 文件的,所以 JSONP 利用了这一点。
它的工作方式大概是这样的:
-
前端定义一个函数,比如叫
handleData(data)
。 -
动态创建一个
<script>
标签,src 指向目标 URL,并带上参数callback=handleData
。 -
后端返回的内容是一个函数调用,例如:
jshandleData({ "name": "张三", "age": 25 });
-
浏览器执行这个函数,拿到数据。
4.2.2 优点 & 缺点:
- ✅ 优点:兼容性好,适用于不支持 CORS 的旧浏览器。
- ❌ 缺点:只能用于 GET 请求;存在安全隐患;代码复杂度高。
4.3 代理服务器(适合开发阶段)
如果你是前端开发者,暂时无法修改后端配置,可以在开发阶段使用"代理服务器"来绕过跨域限制。
4.3.1 原理简述:
代理服务器就像是一个"中间人"。前端把请求发给代理服务器,由它去访问真正的后端接口,然后再把结果返回给前端。
因为代理服务器和后端之间的通信是服务器之间的通信,不受浏览器同源策略限制。
4.3.2 实现方式(以 Vue/React 开发为例):
以 Vue 项目为例,在 vue.config.js
中添加如下配置:
js
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://api.example.com',
changeOrigin: true,
pathRewrite: { '^/api': '' }
}
}
}
}
这样,前端请求 /api/login
,会被代理到 http://api.example.com/login
,从而避免跨域。
4.4 Nginx 反向代理(生产环境常用)
如果你已经上线了项目,想彻底解决跨域问题,可以通过 Nginx 配置反向代理。
4.4.1 原理简述:
Nginx 是一个高性能的 Web 服务器,它可以作为前端服务和后端服务之间的桥梁。前端访问的是同一个域名下的路径,而 Nginx 负责将这些请求转发到对应的后端服务上。
4.4.2 示例配置:
nginx
server {
listen 80;
server_name yourdomain.com;
location /api/ {
proxy_pass http://backend-server/;
}
location / {
root /path/to/your/frontend/dist;
index index.html;
}
}
这样,前端访问 /api/login
,其实是由 Nginx 转发到了后端服务器,前后端都在同一个域名下,自然就不会跨域了。
五、其他注意事项
5.1 简单请求 vs 预检请求(Preflight)
并不是所有的请求都会直接发送过去,有些请求会被浏览器先做一次"预检查",也就是所谓的 Preflight Request(预检请求)。
什么情况下会触发 Preflight?
- 请求方法不是 GET、HEAD、POST;
- POST 请求的 Content-Type 不是 application/x-www-form-urlencoded、multipart/form-data 或 text/plain;
- 请求头中带有自定义头部(如 Authorization);
- 请求中带有 cookie 且 Access-Control-Allow-Credentials 为 true。
这时浏览器会先发送一个 OPTIONS 请求,询问服务器是否允许跨域操作,只有得到肯定答复才会继续发送真实请求。
5.2 Cookie 和认证信息的跨域处理
如果你的请求需要携带 Cookie 或者 Token,也需要特别注意跨域设置。
前端设置:
js
fetch('http://api.example.com/user', {
credentials: 'include'
})
后端设置:
http
Access-Control-Allow-Origin: http://your-frontend-domain.com
Access-Control-Allow-Credentials: true
六、总结一下
方法 | 是否推荐 | 适用场景 | 说明 |
---|---|---|---|
CORS | ✅ 推荐 | 所有场景 | 最标准的方法,需后端配合 |
JSONP | ❌ 过时 | 旧项目兼容 | 只能 GET 请求,不安全 |
代理服务器 | ✅ 推荐 | 开发阶段 | 前端本地配置即可 |
Nginx 反向代理 | ✅ 推荐 | 生产环境 | 安全、稳定、彻底解决问题 |
七、结语
跨域问题看起来有点"玄学",但只要你理解了它是浏览器出于安全考虑才做的限制,就能明白为什么会出现这个问题,以及为什么要用特定的方式来解决。
对于刚入门的前端同学来说,掌握 CORS 和代理服务器这两个方案就已经可以应对大部分开发需求了。等你对整个前后端交互流程更熟悉之后,再慢慢学习 Nginx、安全机制等内容也不迟。
希望这篇博客能帮你彻底搞懂"跨域",不再害怕它!