前端写好了。
API 写好了。
两个都在线。
它们单独工作都很正常。
于是按照互联网传统,它们拒绝互相说话。
控制台丢下一句:
blocked by CORS policy服务器明明存在,URL 也能打开,甚至响应可能已经回来了,浏览器还是不让你的 JavaScript 读取。
这就是 CORS。
先搞懂 Origin
CORS 是 Cross-Origin Resource Sharing,跨源资源共享。所谓 Origin,可以粗略理解成:
协议 + 主机名 + 端口https://example.com 与 https://api.example.com 是不同 Origin。http://example.com 与 https://example.com 也不同。端口不同,同样算不同。
人类看着都像“我自己的网站”,浏览器看着则像三个街区,各自有保安。
为什么浏览器要多管闲事
假设你登录着邮箱或后台,又打开了一个陌生网站。那个页面同样可以运行 JavaScript。如果浏览器允许任何页面随便读取其他站点的响应,恶意页面就可能尝试利用你现有的登录状态访问敏感内容。
CORS 并不是整个 Web 安全模型,但它是浏览器跨源访问控制里很重要的一层。
最关键的一点是:CORS 主要由浏览器执行。
“可是在 Postman 里明明能用”
这恰恰很正常。Postman 和 curl 不是运行在普通网页的同源安全模型里。
curl https://api.example.com/data能成功,而:
fetch("https://api.example.com/data")失败,并不意味着服务器一定挂了。更可能是浏览器在说:服务器没有明确告诉我,这个网页有权读取它的响应。
最常见的响应头
Access-Control-Allow-Origin: https://app.example.com它相当于服务器写了一张许可:这个 Origin 可以读取响应。
完全公开的资源有时会使用:
Access-Control-Allow-Origin: *很方便,但不是所有场景都应该这么写。尤其当请求带 Cookie 或其他凭据时,规则会更严格。
OPTIONS 为什么突然冒出来
你明明写的是 POST,Network 面板却先出现一个 OPTIONS。这通常是 preflight,预检请求。
浏览器先问服务器:
“我等会儿想用这个方法、这些请求头,从这个 Origin 来访问,你同意吗?”
服务器需要用类似下面的响应头回答:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization预检失败时,真正的 POST 甚至不会发送。这也是为什么你翻后端日志,发现“根本没有收到请求”。POST 还在门口排队,OPTIONS 已经和保安吵起来了。
mode: "no-cors" 通常不是解决方案
很多人第一次搜索报错,会看到:
fetch(url, { mode: "no-cors" })报错似乎不见了,但响应通常变成 opaque,你的 JavaScript 不能像原来一样读正文。换句话说,你只是告诉浏览器:
“行,发出去吧,但回来之后也别让我看。”
某些场景有用,但绝不是“关闭 CORS”按钮。
带 Cookie 时更严格
如果请求使用:
credentials: "include"服务端通常还要:
Access-Control-Allow-Credentials: true并且不能再随便用通配符 Origin。认证请求值得比“全互联网都来吧”更谨慎一点。
CORS 也不是身份认证
只允许某个前端域名,并不意味着 API 已经安全。curl、脚本和其他非浏览器客户端仍然可以直接请求后端。
真正的私有数据仍然需要认证和授权:Session、Token、权限判断、API Key 或其他访问控制。
我排 CORS 的顺序
- 请求有没有到服务器。
- 有没有 OPTIONS 预检。
- 预检状态码是什么。
Access-Control-Allow-Origin是否存在且完全匹配。- 方法是否允许。
- 自定义请求头是否允许。
- 是否用了 credentials。
- 中途是否发生重定向。
- CDN、Worker 或代理是否改写了响应头。
Console 告诉你“有事发生”,Network 才更像现场记录。
最后
CORS 第一次出现时很像浏览器故意找茬:API 在,服务器也回了,偏偏不让你拿。
但理解 Origin、预检和响应头之后,它就没那么神秘。
浏览器不是忘了怎么联网。
它只是说:
“我知道 API 在哪。我也知道它回了什么。”
“不过在把内容交给这段 JavaScript 之前,我想先看看书面许可。”