06 CORS 的工作原理
1. CORS 解决的是什么问题
CORS(Cross-Origin Resource Sharing,跨源资源共享)是浏览器对同源策略的一种受控放行机制。
一个源由三部分组成:协议、主机、端口。任何一项不同就是跨源:
页面:http://localhost:5173
接口:http://localhost:8080
^^^^ 端口不同,因此跨源
浏览器会把页面的源放入请求头:
Origin: http://localhost:5173
服务端通过响应头说明是否允许这个源。最终是否把响应交给 JavaScript,由浏览器决定。
CORS 不是阻止请求抵达服务器的防火墙。某些请求已经执行并返回 200,浏览器仍可能禁止 JavaScript 读取响应。
2. CORS、认证与 CSRF 必须分开
| 机制 | 回答的问题 | 典型手段 |
|---|---|---|
| CORS | 哪些网页源可以由浏览器读取响应 | Access-Control-* |
| 认证 | 调用者是谁 | Session、JWT、API Key |
| 授权 | 此人是否可以执行此操作 | 角色、权限、资源归属检查 |
| CSRF 防护 | 浏览器是否被其他网站借用登录凭据发起操作 | SameSite、CSRF Token、Origin 检查 |
curl、Postman、后端程序不执行浏览器同源策略,因此不会被 CORS 拦住。即使只允许公司前端域名,攻击者仍能直接调用 API;接口依旧必须认证和授权。
如果认证依赖浏览器自动携带的 Cookie,还要单独防范 CSRF。Bearer Token 通常由 JavaScript 显式加入 Authorization,攻击网站一般拿不到该 Token,但仍要防范 XSS 和 Token 泄漏。
3. 简单请求
满足浏览器规定的“简单请求”条件时,浏览器直接发送正式请求。例如:
GET /api/users HTTP/1.1
Host: api.example.com
Origin: https://www.example.com
服务端允许后返回:
Access-Control-Allow-Origin: https://www.example.com
Vary: Origin
常见简单方法只有 GET、HEAD、POST;请求头和 Content-Type 也受限制。application/x-www-form-urlencoded、multipart/form-data、text/plain 可能符合简单请求条件,application/json 不属于简单 Content-Type。
“简单请求”不等于安全请求。HTML 表单可以跨源提交 POST,所以有副作用的 Cookie 接口仍须防 CSRF。
4. 预检请求
以下情况常触发预检:
PUT、PATCH、DELETE;- JSON POST;
- 携带
Authorization; - 携带自定义请求头;
- 请求使用某些非简单配置。
浏览器先自动发送 OPTIONS:
OPTIONS /api/users/1 HTTP/1.1
Origin: http://localhost:5173
Access-Control-Request-Method: PATCH
Access-Control-Request-Headers: authorization,content-type
服务端可返回:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://localhost:5173
Access-Control-Allow-Methods: GET,POST,PATCH,DELETE
Access-Control-Allow-Headers: Authorization,Content-Type
Access-Control-Max-Age: 600
Vary: Origin, Access-Control-Request-Headers
浏览器确认允许后,才发送 PATCH。预检失败时,正式请求通常不会发送。
5. 用 curl 重现预检
curl -i -X OPTIONS http://127.0.0.1:8080/api/users/1 \
-H "Origin: http://localhost:5173" \
-H "Access-Control-Request-Method: PATCH" \
-H "Access-Control-Request-Headers: authorization,content-type"
只发送 curl -X OPTIONS 不足以模拟浏览器预检,关键的三个 Header 也要提供。
6. 为什么 Postman 正常而浏览器失败
Postman 能看到 200,只证明网络和接口本身可能正常。浏览器还可能因为以下原因拒绝前端代码:
- 响应缺少
Access-Control-Allow-Origin; - 允许源与页面实际 Origin 不匹配;
- 预检被 JWT 中间件返回 401;
- 携带 Cookie 却使用
Allow-Origin: *; - CSP
connect-src不允许该 API; - 代理给错误响应漏加 CORS Header。
排错时同时查看浏览器 Console、Network 中的 OPTIONS 和正式请求,以及 Nginx/Express 日志。
7. 无 Origin 的请求
同源请求通常不需要 Origin;curl、健康检查、服务间调用也可能没有它。无 Origin 不表示恶意,也不表示可信。
常用策略是:
- 有 Origin:按浏览器来源白名单判断;
- 无 Origin:让请求继续,再由认证、授权等规则判断;
- 如果业务明确只接受浏览器跨源调用,才考虑拒绝无 Origin,但不能把它当作可靠认证,因为非浏览器客户端可以伪造 Origin。
8. 本章结论
CORS 决定浏览器是否把响应交给页面 JavaScript
认证决定调用者是谁
授权决定调用者能做什么
CSRF 防护阻止其他网站借用浏览器凭据