
🚫 那个让前端抓狂的红字报错
作为前端开发,你一定见过这个经典的报错:
Access to XMLHttpRequest at 'http://api.server.com' from origin 'http://localhost:8080' has been blocked by CORS policy
这时候你可能会想:"我就调个接口,为什么浏览器要拦着我?"
其实,这不是浏览器在故意刁难你,而是它在保护用户 。这个机制叫做同源策略 (Same-Origin Policy)。
🏠 什么是"同源策略"?
1. 核心概念
浏览器有一个默认的安全规则:A 网站的 JavaScript,不能随便读取 B 网站的数据。
除非 A 和 B 是"一家人"(同源)。
2. 怎样才算"一家人"?
必须同时满足以下三个条件,才算同源:
- 协议相同 (Protocol):都是
http或都是https。 - 域名相同 (Domain):都是
www.example.com。 - 端口相同 (Port):都是
80(或都不写)。
只要有一个不一样,就是跨域!
| 当前页面 | 请求接口 | 结果 | 原因 |
|---|---|---|---|
http://a.com |
http://a.com/api |
✅ 同源 | 一家人 |
http://a.com |
https://a.com/api |
❌ 跨域 | 协议不同 (http vs https) |
http://a.com |
http://b.com/api |
❌ 跨域 | 域名不同 |
http://a.com |
http://a.com:8080 |
❌ 跨域 | 端口不同 (80 vs 8080) |
👮♂️ 为什么要有这个策略?(比喻)
想象一下:
- 浏览器 = 你的小区 🏘️
- 同源策略 = 小区保安 👮♂️
- 恶意网站 = 推销员 🦹
如果你登录了银行网站 (bank.com),银行给你发了一张通行证 (Cookie) 存在浏览器里。
然后你不小心点开了恶意网站 (evil.com)。如果没有同源策略:
evil.com的代码悄悄向bank.com发送请求:"我要转账"。- 浏览器发现你有
bank.com的通行证,于是就带上通行证发请求了。 - 银行一看通行证是真的,钱就被转走了! 💸
但是,因为有同源策略 (保安) :
保安发现:"哎?你是 evil.com (推销员),你想去 bank.com (金库) 拿东西?不行!你的工牌对不上!"
于是请求被拦截,你的钱保住了。
🔓 怎么解决跨域?(三种常见方案)
虽然同源策略是为了安全,但在开发中,我们的前端 (localhost:8080) 和后端 (api.com) 往往不在同一个域,确实需要通信。怎么办?
1. CORS (官方推荐:发通行证) ✅
CORS 全称是 Cross-Origin Resource Sharing (跨域资源共享)。
原理很简单:后端服务器告诉浏览器:"在这个名单里的人,可以放行。"
流程图解:
🖥️ 后端 (api.com) 🌍 浏览器 (localhost) 🖥️ 后端 (api.com) 🌍 浏览器 (localhost) 准备发送复杂请求 (如带Token) 预检请求 (Preflight) 查白名单:允许!✅ OPTIONS /api (有人在吗?我能进吗?) 200 OK (Header: Allow localhost) GET /api (真正的请求) {data: ...} (拿到数据)
流程:
- 浏览器 :我想去
api.com拿数据,但我来自localhost:8080,行吗? - 浏览器 (如果是复杂请求,先发一个
OPTIONS预检请求):有人在吗?我能进吗? - 后端服务器 :收到,我查一下白名单... 好的,允许
localhost:8080访问!(返回响应头Access-Control-Allow-Origin: http://localhost:8080) - 浏览器 :看到后端点头了,保安放行,数据拿到。
后端代码示例 (Node.js/Express):
javascript
app.use((req, res, next) => {
// 允许 localhost:8080 访问
res.header('Access-Control-Allow-Origin', 'http://localhost:8080');
// 允许的方法
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
next();
});
2. 代理 (Proxy) (欺骗保安) 🎭
同源策略只限制浏览器,不限制服务器! 服务器和服务器之间通信是自由的。
我们可以找一个中间人 (代理服务器),让他去帮我们拿数据。
原理:
- 前端 (
localhost:8080) 不直接找后端,而是找 本地代理 (localhost:8080/api)。 - 保安 一看:你找你自己家的人,同源,放行! ✅
- 本地代理 悄悄去 后端 (
api.com) 把数据拿回来。 - 本地代理 再把数据给 前端。
Vue 配置示例 (vue.config.js):
javascript
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://api.com', // 真正的后端地址
changeOrigin: true, // 告诉后端:我是从 api.com 来的 (伪装)
pathRewrite: { '^/api': '' }
}
}
}
}
3. Nginx 反向代理 (生产环境常用) 🔄
原理和上面一样,只不过是在部署时,用 Nginx 做这个"中间人"。
Nginx 配置:
nginx
server {
listen 80;
server_name my-website.com;
# 前端文件
location / {
root /usr/share/nginx/html;
}
# 接口转发
location /api/ {
proxy_pass http://api-server.com/; # 转发给后端
}
}
这样对浏览器来说,前端和接口都在 my-website.com 下,完美同源!
🧪 进阶知识:面试必问细节
1. 简单请求 vs 复杂请求 (为什么会有 OPTIONS?) 🕵️
有时候你会发现浏览器自动 多发了一个 OPTIONS 请求,这叫预检请求 (Preflight)。
- 简单请求 (Simple Request) :直接发,不预检。
- 方法是
GET,HEAD,POST。 - Header 只有常见的
Accept,Content-Type等。 Content-Type只能是text/plain,multipart/form-data,application/x-www-form-urlencoded。
- 方法是
- 复杂请求 (Preflighted Request) :先发
OPTIONS问路。- 用了
PUT,DELETE方法。 - 发送了
JSON数据 (Content-Type: application/json)。 - 加了自定义 Header (如
Authorization,Token)。
- 用了
结论 :大部分现代前后端分离应用(发 JSON、带 Token)都是复杂请求 ,所以看到 OPTIONS 不要慌,那是浏览器在礼貌问路。
2. 跨域带 Cookie (凭证) 🍪
默认情况下,跨域请求不带 Cookie。如果你需要登录态,必须两边都配合:
- 前端 (axios) :开启
withCredentials: true。 - 后端 :
Access-Control-Allow-Credentials: true(允许带证件)。Access-Control-Allow-Origin不能 是*,必须写死具体域名 (如http://localhost:8080)。
3. JSONP (时代的眼泪) 👴
在 CORS 还没普及的年代,前辈们发现 <script src="..."> 标签不受同源策略限制(你可以引用百度的 JS)。
于是利用 <script> 偷运数据,这就是 JSONP。现在除了老旧系统,基本不用了。
🧠 总结
- 跨域 是因为浏览器的同源策略,为了保护你的安全。
- 同源 = 协议 、域名 、端口 三者都一样。
- 解决办法 :
- CORS:后端加响应头,发"通行证"。(最常用)
- Proxy:前端/Nginx 做代理,让"中间人"去拿数据。(开发/生产环境常用)