DrillLab
第 94 / 105 道94 / 105 · #360

什么是 CORS,怎么解决 CORS 错误

What is CORS and how to solve the CORS error

先自己答,再往下看Answer it yourself first

一句话:浏览器的同源策略默认禁止页面读取 跨源响应;CORS 是服务器通过响应头「授权」某些跨源请求的机制。

三条最关键的认知(这才是区分度):

  • 是浏览器在拦,不是服务器拒绝。请求通常已经发出去了、 服务器也已经处理了—— 只是浏览器不让 JS 读响应。所以看到 CORS 错误不等于接口没执行(非幂等接口尤其要注意, 可能已经创建了数据)。
  • 所以前端改不了。必须服务端加响应头, 或者走代理。在前端加什么请求头都没用。
  • 同源 = 协议 + 域名 + 端口 三者全同。httphttps 不同源,30003001 不同源。

简单请求 vs 预检请求:GET / HEAD /POST 且只用安全头、Content-Type 限于三种 (form-urlencodedmultipart/form-datatext/plain)→ 直接发。
其他情况先发一个OPTIONS 预检注意 application/json就会触发预检—— 这就是为什么「明明是 POST 却多了一个 OPTIONS 请求」。

四种解法:

  1. 服务端加头(正解)——Access-Control-Allow-Origin, Express 里 app.use(cors())
  2. 开发时用 dev server 代理—— Vite 的 server.proxy, 让浏览器以为是同源。
  3. 生产用同源部署或网关—— 前端和 API 挂在同一个域下的不同路径。
  4. (历史方案)JSONP —— 只支持 GET,已淘汰。

会追问:「要带 cookie 怎么办?」—— 前端 credentials: "include", 服务端 Allow-Credentials: true而且此时Allow-Origin不能是 *,必须写具体域名。 这是最常见的「配了 cors 还是不行」的原因。
「预检能缓存吗?」——Access-Control-Max-Age, 避免每个请求都多一次往返。

In one line: the browser’s same-origin policy stops a page from reading a cross-origin response by default; CORS is the mechanism by which the server uses response headers to authorise some of those requests.

Three things to understand — this is what separates people:

  • The browser blocks it; the server did not refuse. The request usually went out and the server usually handled it — the browser just will not let your JS read the response. So a CORS error does not mean the endpoint did not run. Watch out with non-idempotent endpoints: the record may already exist.
  • Which is why the front end cannot fix it. The server has to send the headers, or you go through a proxy. No request header you add on the client will help.
  • Same origin means scheme, host and port all match. http and https are different origins; 3000 and 3001 are different origins.

Simple requests vs preflighted ones: GET / HEAD / POST with only safe headers and a Content-Type limited to three values (form-urlencoded, multipart/form-data, text/plain) go straight out.
Anything else sends an OPTIONS preflight first. Note that application/json triggers a preflight — that is why “it is a POST but I see an extra OPTIONS request”.

Four ways to fix it:

  1. Send the headers from the server (the real fix) — Access-Control-Allow-Origin, or app.use(cors()) in Express.
  2. Proxy through the dev server while developing — Vite’s server.proxy, so the browser thinks it is same-origin.
  3. In production, deploy same-origin or put a gateway in front — front end and API on the same domain, different paths.
  4. (Historical) JSONP — GET only, obsolete.

Follow-up: “What if I need to send cookies?” — credentials: "include" on the client, Allow-Credentials: true on the server, and at that point Allow-Origin cannot be * — it must name the origin. This is the most common reason for “I configured cors and it still does not work”.
“Can the preflight be cached?” — Access-Control-Max-Age, so you do not pay a round trip per request.

JavaScript两种最常用的解法示意Illustrative
1// 服务端(正解)
2app.use(cors({
3 origin: "https://app.example.com", // 带 cookie 时不能用 *
4 credentials: true,
5 maxAge: 86400, // 缓存预检结果
6}));
7
8// 开发时代理(vite.config.ts)
9server: {
10 proxy: { "/api": { target: "http://localhost:4000", changeOrigin: true } },
11}
12// 浏览器看到的是同源的 /api/...,不触发 CORS
1// On the server (the correct way)
2app.use(cors({
3 origin: "https://app.example.com", // with cookies you cannot use *
4 credentials: true,
5 maxAge: 86400, // cache the preflight result
6}));
7
8// A proxy during development (vite.config.ts)
9server: {
10 proxy: { "/api": { target: "http://localhost:4000", changeOrigin: true } },
11}
12// The browser sees /api/... on the same origin, so CORS is never triggered