3个真实案例教你搞定跨域遭到拦截的新手避坑指南
配置环境就卡半天,明明本地跑得好好的,一部署到服务器或者换个浏览器就报错,这种“遭到”拦截的痛苦,谁懂?
很多新手在调试前端请求时,最容易陷入的误区就是死磕代码逻辑,却忽略了浏览器安全机制的底层规则。今天咱们不整虚的,直接拆解 HTTP 预检请求(Preflight Request)这个让无数人头疼的“拦路虎”。
核心痛点直击:为什么你的 fetch 或 axios 请求在控制台里显示 CORS error?为什么加上了 Access-Control-Allow-Origin 还是被浏览器阻断?
这就是一场关于“浏览器安全策略”与“后端响应头”的博弈。搞不懂这个,你的前端项目永远在“遭到”各种奇奇怪怪的网络错误中挣扎。
1. 一句话原理:浏览器是守门员,不是传声筒
很多开发者误以为 CORS(跨域资源共享)是后端的事,其实浏览器才是第一道关卡。
当你的前端页面(比如 https://a.com)试图去请求另一个源(比如 https://b.com)的数据时,浏览器会先检查 b.com 返回的响应头里,有没有明确写着“我允许 a.com 来访问我”。
如果没写,或者写错了,浏览器就会直接拦截这个响应,根本不会把数据交给你的 JavaScript 代码处理。这时候,你在控制台看到的错误,不是网络断了,而是浏览器故意不让你看。
这就是“遭到”拦截的本质:同源策略(Same-Origin Policy)的强制执行。
关键记忆点:CORS 不是前端技术,是浏览器安全机制 + 后端配置 的共同产物。前端能做的只是“发起请求”和“监听错误”,真正决定“放行”还是“拦截”的,是后端返回的 HTTP 响应头。
2. 类比解释:就像寄快递,得看收件人是否签收
想象一下,你(前端代码)想给住在隔壁小区的人(后端服务器)寄个重要文件。
- 简单请求:如果你只是寄个明信片(GET/POST 简单内容),邮局(浏览器)会直接送过去。但如果收件人(后端)在门口贴了告示:“只收本公司快递”,那邮局就会拒收,并告诉你“遭到拒收”。
- 复杂请求(预检):如果你寄的是个大箱子,里面装了敏感物品(比如带自定义 Header 的 POST 请求),邮局会先派个快递员(OPTIONS 预检请求)去问问收件人:“我要送这个大箱子,你收吗?”
- 收件人(后端)必须回复:“收,我允许
a.com的快递员送。” - 只有收到这个明确的“收”的回复,邮局才会真正派车把大箱子送过去。
- 如果收件人没回复,或者回复“不收”,邮局就直接终止流程,箱子根本没发出去。
- 收件人(后端)必须回复:“收,我允许
新手避坑重点:很多新手只盯着“发箱子”(实际请求),却忽略了“问收件人”(预检请求)这一步。如果后端没配置好 OPTIONS 方法的响应,整个流程就会在预检阶段就“遭到”阻断。
3. 源码与伪代码:后端到底该怎么配?
光讲理论没用,直接上代码。这里以 Node.js (Express) 和 Spring Boot 为例,展示如何正确配置 CORS,避免“遭到”浏览器拦截。
场景一:Node.js (Express)
很多新手喜欢手动写中间件,结果漏掉了 OPTIONS 方法,导致预检请求失败。
const express = require('express');
const app = express();// 错误示范:只处理了 GET/POST,没处理 OPTIONS
// app.use((req, res, next) => {
// res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
// res.header('Access-Control-Allow-Methods', 'GET, POST');
// next();
// });// 正确示范:使用 cors 中间件,或者手动完整配置
const cors = require('cors');// 方式1:使用 cors 包(推荐,省心)
app.use(cors({origin: 'http://localhost:3000', // 指定允许的前端地址methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], // 必须包含 OPTIONSallowedHeaders: ['Content-Type', 'Authorization'] // 允许前端自定义的 Header
}));// 方式2:手动配置(用于理解原理)
app.use((req, res, next) => {// 1. 允许跨域来源res.header('Access-Control-Allow-Origin', 'http://localhost:3000');// 2. 允许的请求方法,必须包含 OPTIONS(预检请求)res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');// 3. 允许的 Header,如果前端发了 Authorization,这里必须允许res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');// 4. 如果请求类型是 OPTIONS,直接返回 200,不要进入后续业务逻辑if (req.method === 'OPTIONS') {return res.sendStatus(200);}next();
});app.get('/api/data', (req, res) => {res.json({ message: '数据拿到了,没有遭到拦截' });
});app.listen(8080);
逐行解析:
Access-Control-Allow-Origin: 告诉浏览器,谁可以来访问我。如果是*,表示所有人都可以(但携带凭证时不能用*)。Access-Control-Allow-Methods: 告诉浏览器,我支持哪些 HTTP 方法。切记:必须包含OPTIONS,否则预检请求会 404 或 405,导致“遭到”拦截。Access-Control-Allow-Headers: 如果前端请求里带了自定义 Header(如token),这里必须声明允许,否则浏览器也会拦截。
场景二:Java (Spring Boot)
Java 后端的新手更爱用 @CrossOrigin 注解,但很容易漏掉配置范围。
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/api")
public class UserController {// 错误示范:默认只允许当前类的方法,且可能未配置预检// @CrossOrigin// @GetMapping("/user")// public String getUser() { ... }// 正确示范:明确指定允许的来源、方法和 Header@CrossOrigin(origins = "http://localhost:3000", // 前端地址methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.OPTIONS}, // 必须包含 OPTIONSallowedHeaders = {"Content-Type", "Authorization"})@PostMapping("/user")public String createUser(@RequestBody User user) {return "创建成功,未遭到CORS拦截";}// 处理预检请求@CrossOrigin(origins = "http://localhost:3000")@RequestMapping(method = RequestMethod.OPTIONS)public void handlePreflight() {// 空方法即可,Spring 会自动设置响应头}
}
新手避坑:在 Spring Boot 中,如果使用了 @CrossOrigin,建议全局配置 WebMvcConfigurer,而不是每个接口都加注解,容易遗漏。
4. 流程描述:一次成功的跨域请求发生了什么?
我们用文字流程来还原浏览器视角的“遭到”与“放行”过程。
场景:前端 http://localhost:3000 请求后端 http://localhost:8080/api/data
步骤 1:前端发起请求
JavaScript 代码执行 fetch('http://localhost:8080/api/data', { method: 'POST', headers: { 'Content-Type': 'application/json' } })。
步骤 2:浏览器判断请求类型
- 方法:
POST - Header:
Content-Type: application/json(非简单类型,属于复杂请求) - 源不同:
localhost:3000!=localhost:8080 - 结论:需要发送预检请求(Preflight Request)。
步骤 3:浏览器发送 OPTIONS 请求 浏览器自动发送:
OPTIONS /api/data HTTP/1.1
Host: localhost:8080
Origin: http://localhost:3000
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
步骤 4:后端处理 OPTIONS 后端收到 OPTIONS 请求,检查配置:
- 是否允许
http://localhost:3000? -> 是 - 是否允许
POST方法? -> 是 - 是否允许
Content-TypeHeader? -> 是 - 响应:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 3600
步骤 5:浏览器校验预检响应
Access-Control-Allow-Origin是否匹配请求中的Origin? -> 匹配Access-Control-Allow-Methods是否包含POST? -> 包含Access-Control-Allow-Headers是否包含Content-Type? -> 包含- 结论:预检通过,未遭到拦截。
步骤 6:浏览器发送实际请求
POST /api/data HTTP/1.1
Host: localhost:8080
Content-Type: application/json
{ "name": "test" }
步骤 7:后端处理实际请求
后端返回 JSON 数据,并再次附带 CORS 头(虽然预检通过了,实际响应也需要有 Access-Control-Allow-Origin,否则浏览器还是会拦截响应体)。
步骤 8:浏览器交付数据
JavaScript 的 then 回调函数接收到数据。
如果任何一步“遭到”失败?
- 后端返回 404/405 给 OPTIONS -> 预检失败,实际请求根本不发。
- 后端 OPTIONS 响应头缺失 -> 预检失败。
- 实际请求响应头缺失
Access-Control-Allow-Origin-> 预检通过,但响应被浏览器拦截,前端代码拿到的是undefined或错误,控制台报 CORS Error。
5. 实战验证与进阶避坑
常见“遭到”拦截的三大坑
Access-Control-Allow-Origin设为*但携带了 Cookie- 现象:前端用了
withCredentials: true,后端返回Access-Control-Allow-Origin: *。 - 结果:浏览器直接报错,遭到拦截。
- 解决:当携带凭证时,
Access-Control-Allow-Origin必须指定具体域名,不能是*。同时后端需设置Access-Control-Allow-Credentials: true。
- 现象:前端用了
忽略
Access-Control-Max-Age- 现象:每次页面刷新或请求,都会先发一个 OPTIONS 预检请求,增加延迟。
- 解决:设置
Access-Control-Max-Age: 3600,告诉浏览器缓存预检结果 1 小时,期间不再发送 OPTIONS 请求。
Nginx 反向代理未配置 CORS 头
- 现象:本地开发正常,上线后遭到拦截。
- 原因:生产环境通常通过 Nginx 代理,如果 Nginx 层没有配置 CORS 头,或者后端没配,请求在 Nginx 层就被“截胡”了。
- 解决:在 Nginx 配置中加上:
location / {add_header Access-Control-Allow-Origin $http_origin;add_header Access-Control-Allow-Credentials true;add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS';add_header Access-Control-Allow-Headers 'Content-Type, Authorization';if ($request_method = 'OPTIONS') {return 200;}proxy_pass http://backend; }
如何快速定位是“预检失败”还是“实际请求失败”?
打开浏览器开发者工具(F12)-> Network 面板:
- 看有没有
OPTIONS请求。- 有,且状态码不是 200/204 -> 预检失败,检查后端 OPTIONS 处理逻辑。
- 有,且状态码是 200,但
POST/GET请求显示红色错误 -> 实际请求响应头缺失,检查后端实际接口的响应头。
- 看 Console 面板的错误信息。
Access to XMLHttpRequest at '...' from origin '...' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.-> 典型的响应头缺失。CORS policy: Request header field 'xxx' is not allowed by Access-Control-Allow-Headers in preflight response.-> 预检时 Header 不被允许。
权威参考
关于 CORS 的规范细节,建议查阅 MDN Web Docs 中的 "CORS - Cross-Origin Resource Sharing" 章节。MDN 详细列出了简单请求与复杂请求的区别,以及浏览器处理流程的官方定义,是排查此类问题的权威依据。
新手避坑总结:
- CORS 是浏览器机制,不是前端 bug。
- 复杂请求必须处理 OPTIONS。
- 携带凭证时不能用
*。 - 生产环境注意 Nginx 配置。
互动时间:
你公司项目里是怎么处理跨域问题的?是统一在网关层配置,还是每个微服务单独配?有没有遇到过因为 Access-Control-Max-Age 没设导致接口响应变慢的情况?欢迎在评论区分享你的踩坑经历和解决方案,咱们一起避坑!