Skip to content

CORS 跨域资源共享 · 从 0 到 1 完全指南

一句话开篇:本章用"小区门禁、跨城办事、海关报关"等生活类比,把困扰每个前端的"跨域报错"彻底讲清——你将看懂为什么会跨域、CORS 协议每个响应头到底在做什么、前后端各要配什么、以及面试官最爱追问的细节。


📖 目录


0. 你大概率遇过这个报错

Access to fetch at 'http://api.example.com/users' from origin 'http://localhost:3000'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource.

99% 的前端在第一次连后端接口时都被这条红字劝退过。这条报错的关键信息有 3 个:

  1. 谁挡住了? —— 浏览器(不是后端、不是网关)
  2. 挡住的原因? —— 没看到一个叫 Access-Control-Allow-Origin 的响应头
  3. 从哪来到哪去? —— 从 http://localhost:3000 想去 http://api.example.com

把这 3 个问题搞透,你就掌握了 CORS 的 80%。本章正是围绕它们展开。


1. 生活类比(先建立直觉)

概念生活类比一句话解释
同源策略你住 A 小区,不能随意进 B 小区拿东西浏览器对"不同源"做隔离
源 (Origin)你的小区地址 = 城市 + 街道 + 门牌号协议 + 域名 + 端口
跨域请求你想去 B 小区拿快递JS 想访问别的源的资源
CORSB 小区物业贴了一张白名单:"欢迎 A 小区业主"服务端通过响应头明示"我允许谁"
简单请求直接去敲门、问问就走(GET 一下)浏览器直接发,看响应头放不放行
预检请求 (Preflight)先打个电话问"我能带行李进来吗?"浏览器先发 OPTIONS 探路
凭证 (Credentials)你想带身份证(Cookie)进去必须双方都"额外签字"
Allow-Origin: *"本店欢迎所有人参观"任何源都能访问(不能配 Cookie)
Vary: Origin物业按访客小区不同发不同通行证CDN 缓存按 Origin 区分
JSONP用快递包裹(<script>)夹带回信老办法,仅 GET,已淘汰
代理 (Proxy)你让自家保安去 B 小区取件,再交给你同源前端 → 自家后端 → 转发

💡 先记住一个关键直觉:跨域是浏览器单方面挡的,请求其实已经发出去了,响应也回来了,只是浏览器不让你的 JS 读到结果而已(这点很多人不知道,会引出很多面试题)。


2. 同源策略:浏览器的"门禁规则"

2.1 什么叫"同源"

同源 = 协议 + 域名 + 端口 三者完全相同

任何一个不一样,浏览器就把你判定为"跨源"。

URL AURL B同源吗?原因
http://a.com/page1http://a.com/page2完全相同
http://a.com:80http://a.comhttp 默认 80
http://a.comhttps://a.com协议不同
http://a.comhttp://a.com:8080端口不同
http://a.comhttp://www.a.com子域不同
http://a.comhttp://b.com域名不同
http://a.comhttp://A.COM域名大小写不敏感
http://192.168.1.1http://a.comIP 与域名不等价(即使解析一致)

2.2 同源策略到底限制了什么

不是"任何跨域都被拦",它的边界很精细:

行为是否受限
fetch / XMLHttpRequest 跨源❌ 受限(响应内容不可读)
读取跨源 iframe 的 DOM / contentWindow.document❌ 受限
读取跨源 Cookie / LocalStorage / IndexedDB❌ 受限
跨源 <img src> 加载并显示✅ 不受限(但 canvas 取像素受限)
跨源 <script src> 加载并执行✅ 不受限(错误信息脱敏)
跨源 <link rel="stylesheet">✅ 不受限
<a href> 跳转✅ 不受限
<form> 提交跨域✅ 不受限(但拿不到响应)
window.postMessage 跨源通信✅ 专门用于跨源

🤯 看到这里你可能很迷惑:"既然 <form> 能跨域提交,那 fetch 为什么不行?"

答案:因为 <form> 提交后页面会跳走,发起方拿不到响应;而 fetch 是 JS 在原页面拿响应,才有"偷数据"的风险。同源策略要拦的就是这个"读"的能力。

2.3 没有同源策略会怎么样?

举个真实的攻击场景:

1. 你刚刚在 https://bank.com 登录,浏览器存了 cookie
2. 你又打开了恶意网站 https://evil.com 看新闻
3. evil.com 的 JS 代码偷偷写了:
   fetch('https://bank.com/api/transfer?to=hacker&amount=10000',
         { credentials: 'include' })
4. 浏览器自动带上 bank.com 的 cookie
5. 钱被转走,你还啥都不知道

正是同源策略让上面的 fetch 拿不到响应(甚至请求都能被预检拦下来),才让浏览器成为相对安全的执行环境。


3. 为什么需要 CORS

同源策略很安全,但太"一刀切"了。现实业务中:

  • 公司有多个域名:www.company.com + api.company.com + cdn.company.com
  • 前后端分离开发:本地 localhost:5173 调用 localhost:8080
  • 第三方服务:要调用地图、天气、支付等公开 API
  • 微前端:主应用 app.com 加载子应用 subapp.com

这些都是正当的跨源需求,只能简单粗暴地禁掉吗?

CORS(Cross-Origin Resource Sharing,跨域资源共享) 就是 W3C 给出的标准答案:

服务器在响应头里明示"我允许哪些源访问我",浏览器看到允许就放行,没看到就拦截。

形象地说:

旧规则:  小区门禁 → 一律不让外来车辆进
新规则(CORS):  小区门禁 → 看车上有没有"白名单标牌"

              没标牌 → 拦
              有标牌 → 放行(标牌由小区物业 = 服务端 颁发)

关键认知:CORS 是服务端说了算,前端只是"被动遵守"。前端没法靠改自己的代码绕开 CORS(除非走代理)。


4. CORS 的两类请求:简单请求 vs 预检请求

CORS 把跨域请求分成两类,处理方式截然不同。

4.1 简单请求(Simple Request)

定义:同时满足以下三个条件 → 简单请求

条件允许范围
方法GET / HEAD / POST
Content-Typetext/plain / multipart/form-data / application/x-www-form-urlencoded
请求头只能是 CORS 安全头Accept / Accept-Language / Content-Language / Content-Type(前面 3 种之一)

流程:浏览器直接发请求,由响应头决定能否读响应。

[浏览器] ─── GET /api/users ───────────────► [服务器]
            Origin: http://a.com

[浏览器] ◄── 200 OK ───────────────────── [服务器]
            Access-Control-Allow-Origin: http://a.com
            Body: {...}

✅ 浏览器看到 Allow-Origin 匹配 → JS 拿到响应
❌ 没有或不匹配 → 控制台报错,JS 拿不到

⚠️ 请注意:即使浏览器最终拦了你的 JS,请求已经实际打到了服务器。如果是 POST 一笔订单,服务器可能已经写库了!这就是为什么"删除/修改"类操作要做 CSRF Token 的原因。

4.2 预检请求(Preflight)

不是简单请求 → 浏览器先发一次 OPTIONS 询问,得到许可后才发真请求。

触发预检的常见场景

  • PUT / DELETE / PATCH 等方法
  • Content-Type: application/json(这是 99% 的 REST API!)
  • 加了自定义头部,如 AuthorizationX-Token

完整流程

(第 1 步:预检)
[浏览器] ─── OPTIONS /api/users ──────────► [服务器]
            Origin: http://a.com
            Access-Control-Request-Method: PUT
            Access-Control-Request-Headers: Content-Type, X-Token

[浏览器] ◄── 204 No Content ────────────── [服务器]
            Access-Control-Allow-Origin: http://a.com
            Access-Control-Allow-Methods: GET, POST, PUT, DELETE
            Access-Control-Allow-Headers: Content-Type, X-Token
            Access-Control-Max-Age: 86400

(第 2 步:真请求)
[浏览器] ─── PUT /api/users ──────────────► [服务器]
            Origin: http://a.com
            Content-Type: application/json
            X-Token: xxx
            Body: {...}

[浏览器] ◄── 200 OK ────────────────────── [服务器]
            Access-Control-Allow-Origin: http://a.com

💡 生活类比:预检就像你带行李进高档酒店,门口保安先打电话问前台"这位客人能带 3 个箱子上楼吗?",前台说可以你才能进去。

第一次问完,保安会记一段时间(Max-Age),下次同一位客人带同样行李就不用再问了。

4.3 简单 vs 预检 一图速记

                ┌──────── 跨源 fetch ────────┐
                │                              │
                ▼                              ▼
        是简单请求?                   不是简单请求
  (GET/POST/HEAD 且                  (PUT/DELETE 或
   Content-Type 受限                  application/json
   且无自定义头)                        或自定义头)
                │                              │
                ▼                              ▼
       ┌──────────────┐              ┌────────────────┐
       │ 直接发真请求 │              │ 先发 OPTIONS   │
       │              │              │ 预检通过 →    │
       │ 看响应头放行 │              │ 再发真请求    │
       └──────────────┘              └────────────────┘

5. CORS 完整响应头详解

CORS 一共有 6 个响应头,背下来你就理解整个协议了。

5.1 Access-Control-Allow-Origin(必备)

作用:告诉浏览器"哪个源可以读响应"。

http
# 写法 1:通配符(任何源都可以)
Access-Control-Allow-Origin: *

# 写法 2:明确指定(推荐)
Access-Control-Allow-Origin: https://app.example.com

# 写法 3:动态返回(最实用)
# 后端读取请求的 Origin,校验后原样返回

⚠️ 致命坑Allow-Origin 只能是单个源或 *不支持多个源用逗号分隔!要多个白名单的话,必须后端动态判断后返回。

5.2 Access-Control-Allow-Methods(预检必备)

作用:告诉浏览器"我允许哪些 HTTP 方法"。

http
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS

5.3 Access-Control-Allow-Headers(预检必备)

作用:告诉浏览器"我允许请求里带哪些自定义头部"。

http
Access-Control-Allow-Headers: Content-Type, Authorization, X-Token

💡 注意是响应头允许的请求头,别绕晕。也支持 *(但和 Cookie 一样有限制)。

作用:告诉浏览器"是否允许带凭证(Cookie / HTTP Auth)"。

http
Access-Control-Allow-Credentials: true

⚠️ 一旦设为 trueAllow-Origin 不能为 *,必须是明确的源;同时 Allow-HeadersAllow-Methods 也不能为 *

5.5 Access-Control-Max-Age(性能优化)

作用:告诉浏览器"这次预检结果可以缓存多少秒"。

http
Access-Control-Max-Age: 86400   # 缓存 1 天

不设的话,每个跨域请求都会预检一次,性能会很差。

5.6 Access-Control-Expose-Headers(前端要读自定义响应头时必备)

作用:默认前端只能读 6 个"安全响应头"(Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma)。要读其他响应头(如分页、自定义错误码),必须暴露。

http
Access-Control-Expose-Headers: X-Total-Count, X-Trace-Id
js
const res = await fetch('/api/users');
const total = res.headers.get('X-Total-Count');
// ↑ 如果后端没设 Expose-Headers,这行拿到的是 null

5.7 一图记牢

┌────────────────────────── 服务端 CORS 响应头 ──────────────────────────┐
│                                                                       │
│  Access-Control-Allow-Origin: <源>           ← 哪个源能访问(必备)   │
│  Access-Control-Allow-Methods: GET,POST...   ← 允许的方法(预检)     │
│  Access-Control-Allow-Headers: X-Token...    ← 允许的请求头(预检)   │
│  Access-Control-Allow-Credentials: true      ← 允许带 Cookie          │
│  Access-Control-Max-Age: 86400               ← 预检结果缓存秒数        │
│  Access-Control-Expose-Headers: X-Total...   ← 前端能读的响应头       │
│                                                                       │
└───────────────────────────────────────────────────────────────────────┘

┌────────────────────────── 浏览器自动带的请求头 ────────────────────────┐
│                                                                       │
│  Origin: http://a.com                        ← 当前页面源(必有)     │
│  Access-Control-Request-Method: PUT          ← 预检:将要用的方法     │
│  Access-Control-Request-Headers: X-Token     ← 预检:将要用的头部     │
│                                                                       │
└───────────────────────────────────────────────────────────────────────┘

这是 CORS 最常踩坑的地方,单独拎出来讲。

6.1 默认情况:不带凭证

跨源 fetch 默认不发送 Cookie / HTTP Auth,即使你已经登录了 api.com

js
fetch('https://api.com/me');
// 即使你在 api.com 已登录,这次请求也不会带 cookie

前端credentials: 'include'

js
fetch('https://api.com/me', {
  credentials: 'include',   // 关键
});

// axios:
axios.get('/me', { withCredentials: true });

// 老 XHR:
xhr.withCredentials = true;

后端响应必须同时满足:

http
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://app.com    # 不能用 *
Access-Control-Allow-Headers: Content-Type      # 不能用 *
Access-Control-Allow-Methods: GET, POST         # 不能用 *

Cookie 自身还要满足:

http
Set-Cookie: token=xxx; SameSite=None; Secure; HttpOnly

注意三件事:

  1. SameSite=None 才允许跨站发送(默认 Lax 不行)
  2. SameSite=None 必须配合 Secure,意味着必须 HTTPS
  3. HttpOnly 防 XSS 偷 cookie,强烈建议加

6.3 一张图看懂"credentials 配错的坑"

前端 credentials: 'include'  +  后端 Allow-Credentials: true ?
        │                              │
        ├── 都对  ───────► ✅ 带 cookie,能读响应

        ├── 前端没加  ───► ❌ 不带 cookie(请求没带身份证)

        ├── 后端没设  ───► ❌ 浏览器拒绝读响应(CORS 错误)

        └── Allow-Origin: * + 带凭证 → ❌ CORS 报错
                                         "不能在带凭证时用通配符"

7. 服务端配置实战

CORS 是服务端的事,前端调不通的根因 99% 在后端没配对。下面给出常见后端的配置。

7.1 Node.js 原生 HTTP

js
const http = require('http');

http.createServer((req, res) => {
  // ⭐ CORS 头
  res.setHeader('Access-Control-Allow-Origin', 'http://localhost:5173');
  res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization');
  res.setHeader('Access-Control-Allow-Credentials', 'true');
  res.setHeader('Access-Control-Max-Age', '86400');

  if (req.method === 'OPTIONS') {
    res.writeHead(204);
    return res.end();
  }

  res.setHeader('Content-Type', 'application/json');
  res.end(JSON.stringify({ ok: true }));
}).listen(3001);

7.2 Express + cors 中间件(推荐)

bash
npm i cors
js
const express = require('express');
const cors = require('cors');
const app = express();

// 方式 1:粗暴允许所有
app.use(cors());

// 方式 2:精细控制
app.use(cors({
  origin: ['http://localhost:5173', 'https://app.example.com'],
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  exposedHeaders: ['X-Total-Count'],
  maxAge: 86400,
}));

// 方式 3:动态白名单
const whitelist = new Set(['http://localhost:5173', 'https://app.example.com']);
app.use(cors({
  origin: (origin, cb) => {
    if (!origin || whitelist.has(origin)) cb(null, true);
    else cb(new Error('Not allowed by CORS'));
  },
  credentials: true,
}));

app.listen(3001);

7.3 Spring Boot

java
@RestController
@CrossOrigin(origins = "http://localhost:5173", allowCredentials = "true")
public class UserController {
    @GetMapping("/users")
    public List<User> list() { ... }
}

// 或全局配置
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
            .allowedOrigins("http://localhost:5173")
            .allowedMethods("*")
            .allowedHeaders("*")
            .allowCredentials(true)
            .maxAge(86400);
    }
}

7.4 Nginx 反向代理 + CORS 头

nginx
server {
  listen 80;
  server_name api.example.com;

  location / {
    # 处理预检
    if ($request_method = OPTIONS) {
      add_header Access-Control-Allow-Origin $http_origin always;
      add_header Access-Control-Allow-Credentials true always;
      add_header Access-Control-Allow-Methods 'GET,POST,PUT,DELETE,OPTIONS' always;
      add_header Access-Control-Allow-Headers 'Content-Type,Authorization' always;
      add_header Access-Control-Max-Age 86400 always;
      return 204;
    }

    # 真实请求
    add_header Access-Control-Allow-Origin $http_origin always;
    add_header Access-Control-Allow-Credentials true always;

    proxy_pass http://backend;
  }
}

💡 用 $http_origin 动态回显请求源,比硬编码灵活;记得加 always 让错误响应也带头。


8. 前端配置实战

8.1 fetch

js
// 简单跨域 GET
const res = await fetch('https://api.example.com/users');

// 带 cookie 的跨域请求
const res = await fetch('https://api.example.com/me', {
  credentials: 'include',
});

// JSON POST(会触发预检)
const res = await fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer ' + token,
  },
  body: JSON.stringify({ name: 'Alice' }),
  credentials: 'include',
});

// 读取自定义响应头(要后端 Expose-Headers)
const total = res.headers.get('X-Total-Count');

credentials 的三个值:

含义何时用
'omit'不带 cookie公开 API(默认值)
'same-origin'同源带,跨源不带fetch 默认值
'include'一律带跨源也要带 cookie

8.2 axios

js
import axios from 'axios';

const api = axios.create({
  baseURL: 'https://api.example.com',
  withCredentials: true,    // 全局带 cookie
  timeout: 10000,
});

// 单次覆盖
api.get('/users', { withCredentials: false });

8.3 XMLHttpRequest(很少手写了,写库时见)

js
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://api.example.com/users');
xhr.withCredentials = true;
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify({ name: 'Alice' }));

9. 开发期最常用:本地代理

开发期 localhost:5173 → localhost:8080 跨域,最优雅的方法是让前端不知道自己跨域

9.1 Vite 代理

js
// vite.config.js
export default {
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },
};

前端写:

js
// 看起来是同源请求 ↓
fetch('/api/users');
// 实际由 Vite dev server 转发到 http://localhost:8080/users
// 浏览器一直认为是同源,零 CORS 问题

9.2 Webpack devServer 代理

js
// webpack.config.js
module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        pathRewrite: { '^/api': '' },
      },
    },
  },
};

9.3 Next.js rewrites

js
// next.config.js
module.exports = {
  async rewrites() {
    return [
      { source: '/api/:path*', destination: 'http://localhost:8080/:path*' },
    ];
  },
};

9.4 为什么代理能绕过 CORS?

[ 浏览器 ]
 │  fetch('/api/users')   ← 请求自家域名(http://localhost:5173)

[ Vite Dev Server ] ← Node 服务器,**没有同源策略**!
 │  代为请求 http://localhost:8080/users

[ 后端 ]

关键认知:同源策略只存在于浏览器,Node / 浏览器扩展 / curl / Postman 一概不受限。代理就是把"跨域"这件事偷偷搬到了"非浏览器环境"里完成。

⚠️ 生产环境要用 Nginx 做同样的事,别指望生产环境前端 dev server 帮你代理。


10. 其他跨域方案大盘点

方案原理适用场景优缺点
CORS服务端响应头主流 REST API通用、安全、要后端配合
JSONP<script> 不受同源限制老 IE / 老接口仅 GET、不安全(恶意 JS)、已淘汰
代理(dev)前端 dev server 转发本地开发仅开发期,零 CORS 配置
Nginx 反向代理网关同域转发生产部署最常用、零浏览器限制
postMessagewindow 间消息通信跨源 iframe / 新窗口双方主动通信、需要约定协议
WebSocket升级后无同源策略实时通信不走 HTTP 后无 CORS,但握手要看 Origin
CORS Anywhere(公开代理)第三方代理服务临时调试不安全、有限流、绝不能用于生产
document.domain同主域不同子域老项目已废弃,仅 a.com 与 b.a.com
window.name利用 name 属性跨页传值极特殊场景已淘汰
iframe + 主域类似 document.domain老项目已淘汰

10.1 JSONP 怎么实现(理解原理用,别再用)

html
<!-- 客户端 -->
<script>
function handleData(data) {
  console.log('收到数据:', data);
}
</script>
<script src="https://api.example.com/users?callback=handleData"></script>

服务端返回的不是 JSON,而是一段 JS:

js
// https://api.example.com/users?callback=handleData 实际返回:
handleData({ id: 1, name: 'Alice' });

利用 <script> 标签不受同源限制的特性,让服务端"包装"数据为函数调用。只能 GET,且如果服务端返回恶意脚本你就完了

10.2 postMessage 跨源 iframe 通信

js
// 父页面 a.com 发消息
const iframe = document.querySelector('iframe');
iframe.contentWindow.postMessage(
  { type: 'login', token: 'xxx' },
  'https://b.com'    // ⭐ 目标源校验
);

// iframe(b.com)收消息
window.addEventListener('message', (e) => {
  if (e.origin !== 'https://a.com') return;   // ⭐ 必校验来源
  console.log('收到:', e.data);
  e.source.postMessage({ ack: true }, e.origin);
});

⚠️ 安全要点:双向校验 origin,目标 origin 不能写 '*'(除非你真的不在乎被任何源接收)。


11. ⚠️ 易踩的坑(Top 15)

  1. 预检失败但报错信息说不清 —— OPTIONS 状态非 2xx 都算失败,先用 Network 看 OPTIONS 状态码。
  2. 后端代码加了 CORS 头但 Nginx 又加一次 —— 浏览器看到两个 Allow-Origin 直接拒绝。
  3. Allow-Origin: * + credentials: include —— 永远报错,记住"带 cookie 不能用 *"。
  4. Allow-Origin 写多个源 —— 不允许,只能动态返回单个或 *
  5. 以为后端没收到请求 —— 实际请求已经到了!只是浏览器拦了响应。删除/修改类操作要做幂等。
  6. 预检请求 OPTIONS 没经过权限校验 —— 后端 OPTIONS 应该跳过鉴权,只返回 CORS 头。
  7. Cookie 的 SameSite=Lax —— 默认 Lax 跨站不发 cookie,要 None+Secure。
  8. 本地用 localhost vs 127.0.0.1 —— 视为不同源,调试时容易混。
  9. Origin 大小写敏感 —— 后端校验白名单时记得小写化对比。
  10. CDN 缓存了 CORS 响应 —— 必须设 Vary: Origin,否则不同源命中同一份缓存出错。
  11. WebSocket 也校验 Origin —— 服务端要看 Origin 头,否则任何站都能连。
  12. iframe 嵌入跨域页面读不到 DOM —— 即使你能加载,也读不到 contentDocument。
  13. 重定向后丢失 CORS 头 —— 跨域请求被 302 后,新位置必须也带 CORS 头。
  14. 图片 CORS 与 canvas tainted —— 跨域图加载没问题,但画到 canvas 后 toDataURL 会抛错;需要 <img crossorigin="anonymous"> + 后端配合。
  15. fetch 不会自动 reject CORS 错误吗? —— 会 reject,且错误信息很简陋(出于安全),细节只能在 DevTools Console 看。

12. 安全相关:CORS 不等于安全

很多人误以为"开了 CORS 后端就安全了",完全错误

误区真相
CORS 能防 CSRF❌ 不能!CSRF 利用的是 <form>/<img> 等不受 CORS 限制的方式
CORS 能防爬虫❌ 不能!爬虫用 Node/Python 直接请求,根本没浏览器
CORS 是"权限控制"❌ 不是!它只控制"谁能用浏览器读响应",不影响"谁能调用接口"
Allow-Origin: * 危险⚠️ 只对带凭证的接口危险;对公开 API 没问题

正确做法

  • 鉴权用 Token / Cookie
  • 防 CSRF 用 SameSite Cookie + CSRF Token
  • 防爬虫用 限流 + 反机器人
  • CORS 只是给浏览器划线,让 JS 能或不能读响应

13. 完整请求生命周期图


14. 一句话总结

CORS 是浏览器对跨域 fetch / XHR 的安全护栏,靠服务端在响应头里写 Access-Control-Allow-* 系列字段来"放行"。简单请求直接发复杂请求先 OPTIONS 预检;带 Cookie 时前后端都要签字Allow-Origin 不能写 *。开发期最舒服的解法是 Vite/Webpack 代理,生产环境用 Nginx 反向代理


15. 延伸阅读


📂 本目录其他资源

  • qa.md - 面试高频题集(含考察点、加分回答、追问)
  • examples/ - 7 个可独立运行的代码示例(Node 服务端 + 各种前端代码)
  • demo/index.html - 双击即可打开的 CORS 流程交互演示
  • SETUP.md - 如何在本地把示例跑起来