CORS 跨域与 Options 预检请求:为什么有时会先发一个 OPTIONS?
面试速答(30 秒版 TL;DR)
- 跨域本质上不是“请求发不出去”,而是 浏览器的同源策略限制前端 JS 读取跨源响应。
- CORS(Cross-Origin Resource Sharing)是一套浏览器与服务器协作的跨域授权机制,核心靠的是一组响应头。
- 简单请求不会预检;非简单方法、非简单头、某些非简单 Content-Type 往往会触发
OPTIONS预检。 - 预检请求的目的不是拿业务数据,而是先问服务器:这个跨域请求你允不允许、允许哪些方法和头。
- 面试高频坑:
Access-Control-Allow-Origin: *不能和Access-Control-Allow-Credentials: true同时使用。
一、先分清“跨域”到底限制了什么
很多人以为跨域是“浏览器不能发请求”,这不准确。
更准确的说法是:
- 浏览器可以把请求发出去
- 服务器也可能正常响应
- 但如果不满足同源策略和 CORS 规则,浏览器会阻止前端脚本读取响应
所以跨域是 浏览器安全模型,不是 TCP/IP 层面的障碍。
二、什么叫同源
通常要同时满足这三项:
- 协议相同
- 域名相同
- 端口相同
例如:
https://a.com:443/pagehttps://a.com/api
这两个通常算同源。
但如果变成:
http://a.comhttps://a.com
协议不同,就跨源了。
三、CORS 是怎么工作的
最简化理解:
- 浏览器发跨域请求时,会自动带上
Origin - 服务端用
Access-Control-*响应头表态 - 浏览器根据这些头决定是否把响应交给 JS
四、什么是简单请求
简单请求通常满足这些条件:
- 方法是
GET、HEAD、POST - 请求头是浏览器允许的简单头
Content-Type属于简单类型之一,例如:text/plainapplication/x-www-form-urlencodedmultipart/form-data
这类请求一般不会先发预检。
五、什么情况下会触发预检请求
典型触发条件:
- 使用了
PUT、DELETE、PATCH等非简单方法 - 自定义请求头,例如
Authorization、X-Trace-Id Content-Type: application/json- 某些需要凭证的复杂跨域场景
这时浏览器会先自动发一个 OPTIONS 请求。
六、OPTIONS 预检到底在问什么
预检请求一般会带这些头:
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: authorization, content-type
它本质上在问服务端三件事:
- 这个源能不能访问你
- 这个方法能不能用
- 这些请求头能不能带
服务端如果允许,会返回类似:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 600
然后浏览器才会决定是否发送真正的业务请求。
七、最常见的 CORS 响应头
Access-Control-Allow-Origin
允许哪个源访问。
可以是具体源,例如:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials
是否允许携带凭证,例如 Cookie、Authorization 头对应的认证场景。
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods
允许的跨域方法列表。
Access-Control-Allow-Headers
允许的非简单请求头列表。
Access-Control-Expose-Headers
默认情况下,前端 JS 能读取的响应头是有限的;如果你希望 JS 读取自定义响应头,需要显式暴露。
Access-Control-Max-Age
预检结果可缓存多久。
这个值越合适,重复预检带来的额外 RTT 越少。
八、为什么有时后端明明返回了 200,前端还是报跨域
因为问题不在业务处理成不成功,而在:
- 响应头是否正确
- 是否允许当前
Origin - 是否允许携带的头/方法
- 是否在有凭证场景下错误使用了
*
所以经常会出现:
- Network 面板看见接口成功返回
- 控制台却报 CORS error
这是典型的浏览器拦截“响应暴露”。
九、Cookie、Token 与 CORS 怎么一起讲
Cookie 跨域场景
如果前端想跨域携带 Cookie,需要同时满足:
- 前端请求开启
credentials - 服务端返回
Access-Control-Allow-Credentials: true Access-Control-Allow-Origin不能是*- Cookie 本身还要满足
SameSite、Secure等策略
例如前端:
fetch("https://api.example.com/user", {
credentials: "include",
});
Token 场景
如果把 Token 放在 Authorization 头里,通常会触发预检,因为它属于非简单头。
高频题标准答法
1. 为什么会先发一个 OPTIONS?
因为浏览器发现这是一个非简单跨域请求,需要先确认服务端是否允许这个源、方法和请求头,避免直接发业务请求带来安全风险。
2. 表单 POST 为什么有时不预检,JSON POST 却预检?
因为 application/x-www-form-urlencoded、multipart/form-data 属于简单请求允许的内容类型;
application/json 通常不属于简单请求,因此常触发预检。
3. 服务端设置 Access-Control-Allow-Origin: * 就万事大吉吗?
不是。
如果有凭证需求,这样配置反而不合法;另外它也无法精细控制来源。
4. CORS 能防接口被别人直接调用吗?
不能。
CORS 主要限制的是浏览器脚本读取响应,不是替代鉴权。服务端该做的认证授权一点都不能少。
易错点 / 坑
- 把跨域理解成“请求不能发出去”。
- 忽略
OPTIONS响应本身也要带正确的 CORS 头。 Allow-Origin: *和Allow-Credentials: true乱配。- 以为 CORS 能替代身份认证。
速记要点(可背诵)
- 跨域是 浏览器同源策略 问题。
- CORS 核心是:浏览器带
Origin,服务端回Access-Control-*。 - 复杂跨域请求会先发
OPTIONS预检。 Authorization、application/json、PUT/DELETE常是预检触发点。