Skip to content

CORS · 面试题集

收录 20 道高频 CORS 面试题,覆盖原理、协议细节、坑、安全。每题按 考察点 → 标准答案 → 通俗理解 → 加分回答 → 追问 组织,方便速记和应答。


Q1:什么是同源策略?为什么浏览器要有这个东西?

考察点:浏览器安全模型、Web 基础

标准答案同源 = 协议 + 域名 + 端口 三者完全相同。同源策略 (Same-Origin Policy) 是浏览器的一项安全机制,限制:

  • 不同源的 JS 不能读取对方的 fetch / XHR 响应
  • 不能读取不同源 iframe 的 DOM
  • 不能读取不同源的 Cookie / LocalStorage / IndexedDB

通俗理解: 就像不同小区不能随意串门。如果没有这条规则,你登录银行后访问恶意网站,恶意网站的 JS 就能读到你银行账户的余额。

加分回答

  • 同源策略不限制资源加载<script> <img> <link> 都能跨源加载,只是 JS 拿不到内容
  • 同源策略只在浏览器里有效,Node / 爬虫 / Postman 不受限
  • 跨源 fetch 请求其实发出去了,只是浏览器拦了响应

追问

  • http://a.com:80http://a.com 同源吗?(同源,80 是 http 默认端口)
  • http://a.comhttp://www.a.com 同源吗?(不同源,子域不同)

Q2:什么是 CORS?它解决了什么问题?

考察点:CORS 协议本质

标准答案: CORS(Cross-Origin Resource Sharing,跨域资源共享)是 W3C 标准,让服务端可以通过响应头明示哪些源可以访问自己,从而在保留同源策略安全性的前提下允许合法的跨域访问。

核心机制:服务端响应头 Access-Control-Allow-Origin: <源>,浏览器看到匹配就放行 JS 读响应。

通俗理解: 小区门禁默认不让外人进,但物业可以贴个告示牌"欢迎 A 小区业主",A 小区的人就能进了。

加分回答

  • CORS 是服务端说了算,前端无法靠改自己代码绕开
  • CORS 把请求分成简单请求预检请求两类
  • CORS 不防 CSRF、不防爬虫,只是控制"浏览器里 JS 能不能读响应"

追问

  • 为什么不直接放开同源策略?(安全),CORS 比直接放开多了什么?(细粒度的服务端授权)

Q3:什么是简单请求?什么是预检请求?

考察点:CORS 协议核心细节

标准答案

简单请求:同时满足三个条件

  1. 方法是 GET / HEAD / POST
  2. Content-Typetext/plain / multipart/form-data / application/x-www-form-urlencoded
  3. 没有自定义请求头

→ 浏览器直接发请求,看响应头 Access-Control-Allow-Origin 决定是否放行响应。

预检请求 (Preflight):不满足上面任一条件 → 浏览器先发一次 OPTIONS 询问,得到允许后才发真请求。

预检的三个关键请求头

http
Origin: https://a.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, X-Token

预检的关键响应头

http
Access-Control-Allow-Origin: https://a.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type, X-Token
Access-Control-Max-Age: 86400

通俗理解: 简单请求 = 直接敲门借东西;预检 = 先打电话问"我能上楼借东西吗?",对方同意了才上去。

追问

  • Content-Type: application/json 的 POST 是简单请求吗?(不是!会预检)
  • 怎么减少预检的性能损耗?(设置 Access-Control-Max-Age 让浏览器缓存预检结果)

Q4:CORS 一共有哪些响应头?分别什么作用?

考察点:协议规范掌握度

标准答案

响应头作用必备场景
Access-Control-Allow-Origin允许的源所有跨域响应
Access-Control-Allow-Methods允许的方法预检响应
Access-Control-Allow-Headers允许的请求头预检响应
Access-Control-Allow-Credentials是否允许带 Cookie带凭证时
Access-Control-Max-Age预检结果缓存时长(秒)优化用
Access-Control-Expose-Headers前端能读的响应头白名单前端要读自定义响应头时

追问

  • Access-Control-Allow-Origin 能写多个源吗?(不能,只能单个或 *,要多个源要后端动态判断回显)
  • 默认前端能读哪些响应头?(CORS 安全 6 个:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma)

考察点:CORS 凭证机制

标准答案

需要前端、后端、Cookie 自身三方同时配合:

前端

js
fetch(url, { credentials: 'include' });
// axios: { withCredentials: true }

后端

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

Cookie 自身

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

通俗理解: 带身份证进高档酒店:你要主动出示(前端 include)+ 酒店得允许带身份证(后端 Allow-Credentials)+ 身份证得是有效的(SameSite=None+Secure)。

  • Allow-Origin: *Allow-Credentials: true 不能共存,浏览器直接报错
  • SameSite=None 必须配 Secure,意味着必须 HTTPS
  • fetch 默认 credentials: 'same-origin',不显式写 include 跨源不带 cookie

追问

  • SameSite 三个值有什么区别?(Strict 完全禁止跨站、Lax 导航请求允许、None 一律允许但需 Secure)
  • 为什么 Allow-Origin: * 不能配凭证?(安全:防止恶意站点拿到任意源的私密数据)

Q6:跨域请求被 CORS 拦了,请求其实发出去了吗?

考察点:对 CORS 真实行为的理解(高频追问)

标准答案

分两种情况

情况请求是否到达后端
简单请求请求实际打到了后端,后端可能已处理。只是浏览器拦了响应不让 JS 读
预检请求⚠️ 先发 OPTIONS,OPTIONS 失败则真请求根本不发;OPTIONS 成功才发真请求

意味着

  • GET /api/delete?id=1 这种简单请求,即使前端报 CORS 错,后端可能已经把数据删了
  • 这就是为什么"删除/修改"操作要做 CSRF Token + 不要用 GET 修改数据

加分回答: 预检相当于一道"前置安全门",过滤掉了带自定义头/JSON 体的恶意请求。但简单请求没有这道门,依然可被滥用。

追问

  • CSRF 攻击常用什么方法?(GET、POST application/x-www-form-urlencoded,都是简单请求范围)

Q7:怎么解决跨域问题?请列出至少 5 种方案

考察点:综合方案能力

标准答案

方案原理适用场景
CORS服务端响应头主流方案
JSONP<script> 加载,回调函数接收老接口、仅 GET
开发期代理Vite/Webpack devServer 转发本地开发
Nginx 反向代理同域路径转到不同后端生产部署
postMessage跨源 iframe 消息通信第三方嵌入
WebSocket升级后无同源策略实时通信
document.domain同主域不同子域⚠️ 已废弃

追问

  • JSONP 为什么只能 GET?(利用 <script src> 加载,src 只能是 GET)
  • 生产环境为什么推荐 Nginx 而不是 CORS?(同域请求性能更好、避免预检、配置统一)

Q8:开发环境的代理(Vite proxy)为什么能绕过 CORS?

考察点:理解同源策略边界

标准答案

同源策略是浏览器的限制,对 Node / curl / 其他后端完全不存在

代理流程:

浏览器 ──── /api/users ───► Vite Dev Server (Node) ──── /users ───► 后端
       (同源,无 CORS)                                     (Node→Node 无 CORS)

浏览器只看到自己在请求 localhost:5173,是同源的;真正的跨域请求由 Node 替它完成。

通俗理解: 你不能进 B 小区,但你可以让自家保安帮你跑腿。保安去 B 小区不受小区门禁限制(保安不是访客)。

追问

  • 生产环境为什么不能用这种代理?(dev server 不上生产,要用 Nginx 做同样的事)
  • changeOrigin 是干什么的?(把请求的 Host 头改成目标地址,避免后端按 Host 校验时拒绝)

Q9:为什么 <form> 提交可以跨域,fetch 不行?

考察点:同源策略的设计哲学

标准答案

<form> 提交后页面会整体跳转,发起方页面销毁了,拿不到响应fetch 是 JS 在原页面发请求并读响应,有"读"的能力,是同源策略要拦的。

同源策略的核心是**"读"的隔离**,而不是"发"的隔离。能"发不能读"对应:

行为能跨域能读响应
<form> 提交❌(跳走了)
<img src>❌(只能"看" pixel,不能"读")
<script src>❌(执行了但读不到原文)
fetch / XHR默认 ❌✅(如果允许)

追问

  • 为什么图片能跨域显示但画到 canvas 后不能 toDataURL?(同样是"读"的隔离,canvas 像素被认为是读了图片内容)

Q10:CORS 能防 CSRF 吗?

考察点:安全边界、面试官最爱挖坑题

标准答案

不能。CORS 和 CSRF 不是一回事。

CSRF 利用的是浏览器自动带 Cookie的特性 + 不受 CORS 限制的请求方式<form> <img> 等)。即使你后端开了 CORS,攻击者只要让用户在 evil.com 提交一个表单到 bank.com,浏览器照样会带 bank.com 的 cookie。

正确的 CSRF 防御

  1. Cookie 加 SameSite=Lax/Strict(核心防线,现代浏览器默认 Lax)
  2. CSRF Token(一次性令牌,存在 form/header 里)
  3. 校验 Referer / Origin(次要手段)
  4. 关键操作二次验证(输入密码 / 短信验证码)

对比

维度CORSCSRF 防护
解决问题浏览器能不能读跨源响应攻击者能不能伪造已登录用户的请求
谁主导浏览器 + 服务端服务端 + 浏览器(SameSite)
是否防 CSRF

追问

  • 为什么 SameSite=Lax 能防 CSRF?(跨站发起的 POST 不会带 cookie,攻击者拿不到登录态)

Q11:什么是 Vary: Origin?为什么 CDN 场景必须设?

考察点:CDN + CORS 缓存陷阱

标准答案

Vary 响应头告诉缓存层(CDN、浏览器)"响应内容会因哪些请求头变化而不同"。

不设 Vary: Origin 的话:

  • A 源访问 /api/data,CDN 缓存了响应(含 Allow-Origin: A
  • B 源访问 /api/data,CDN 直接返回缓存的响应(依然带 Allow-Origin: A
  • B 源浏览器看到不匹配 → CORS 错误

正确做法

http
Access-Control-Allow-Origin: https://a.com
Vary: Origin

CDN 看到 Vary: Origin 后,会按不同 Origin 分别缓存。

追问

  • Allow-Origin: * 还需要 Vary 吗?(不需要,因为响应不随 Origin 变)

Q12:CORS 失败的请求,前端怎么调试?

考察点:实战调试能力

标准答案

调试步骤

  1. 打开 Chrome DevTools → Network
  2. 过滤 OPTIONS 请求,看预检响应:
    • 状态码是不是 2xx?
    • 是否含 Access-Control-Allow-Origin
    • Allow-Methods Allow-Headers 是否覆盖了你要用的?
  3. 看真请求响应头,是否有 Allow-Origin 且匹配
  4. 看 Console 红字提示,里面写得很清楚缺什么
  5. 用 curl 模拟,确认后端到底返回什么:
    bash
    curl -I -X OPTIONS https://api.com/users \
      -H "Origin: https://app.com" \
      -H "Access-Control-Request-Method: PUT"
  6. 对比同源调用:用 Postman 调一下,能成说明不是接口问题,是 CORS 配置问题

常见错误对应

控制台错误原因
No 'Access-Control-Allow-Origin'后端没设
Allow-Origin: '*' but credentials带 cookie 不能用 *
Method PUT is not allowedAllow-Methods 缺 PUT
Header X-Token is not allowedAllow-Headers 缺 X-Token
Request requires preflight, which is disallowedOPTIONS 被服务端拒了

Q13:JSONP 怎么实现?为什么不安全?

考察点:理解原理(不要求实战用)

标准答案

实现

js
function jsonp(url, callbackName) {
  return new Promise((resolve, reject) => {
    window[callbackName] = (data) => {
      resolve(data);
      delete window[callbackName];
      script.remove();
    };
    const script = document.createElement('script');
    script.src = `${url}?callback=${callbackName}`;
    script.onerror = reject;
    document.body.appendChild(script);
  });
}

jsonp('https://api.com/users', 'handleUsers');

服务端返回:

js
handleUsers({ id: 1, name: 'Alice' });

为什么不安全

  • 服务端返回的是可执行 JS,恶意服务端可以注入任意代码
  • 只能 GET,无法做幂等的修改
  • 错误信息脱敏,不好调试
  • 不支持现代浏览器的鉴权方式

追问

  • 为什么 <script> 标签能跨域?(同源策略不限制资源加载,只限制 JS 读取响应)

Q14:postMessage 跨源通信怎么用?要注意什么?

考察点:跨源 iframe 通信、安全意识

标准答案

发送方

js
iframe.contentWindow.postMessage(
  { type: 'login', token: 'xxx' },
  'https://b.com'    // ⭐ 必须明确目标源
);

接收方

js
window.addEventListener('message', (e) => {
  if (e.origin !== 'https://a.com') return;   // ⭐ 必校验来源
  console.log('收到:', e.data);
});

安全要点

  1. 目标 origin 不要写 '*',否则任何监听者都能收到
  2. 接收时必须校验 e.origin,避免恶意网站发假消息
  3. 传输的数据要做类型校验,别直接 eval 或 innerHTML

应用场景

  • 父页面与 iframe 通信(OAuth 登录、第三方支付页)
  • 主应用与微前端子应用通信
  • 同源不同 tab 间通信(结合 BroadcastChannel)

Q15:跨域图片画到 canvas 后报 "tainted" 错怎么办?

考察点:图片 CORS 与 canvas 安全

标准答案

跨域图片可以加载、显示,但画到 canvas 后调用 toDataURL() / getImageData() 会抛安全错误,因为浏览器认为这是"读取了跨源资源内容"。

解决

html
<img src="https://cdn.com/x.png" crossorigin="anonymous">

加上 crossorigin 属性后,浏览器以 CORS 方式请求图片,同时服务端必须返回 Access-Control-Allow-Origin

js
// JS 创建图片同理
const img = new Image();
img.crossOrigin = 'anonymous';
img.src = 'https://cdn.com/x.png';
img.onload = () => {
  ctx.drawImage(img, 0, 0);
  canvas.toDataURL();   // ✅ 不会 tainted
};

追问

  • crossorigin 三个值(anonymous / use-credentials / 无)的区别?

Q16:WebSocket 也受同源策略约束吗?

考察点:WebSocket 与 CORS 的关系

标准答案

WebSocket 不受同源策略约束,可以跨源连接任何 WebSocket 服务器。但浏览器会在握手请求里带 Origin 头,服务端有责任校验 Origin 决定是否接受连接。

浏览器 ──── WebSocket 握手 (HTTP Upgrade) ───► 服务端
        Origin: https://a.com

服务端:
  if (origin in whitelist) 接受连接
  else 拒绝

追问

  • 为什么 WebSocket 不像 fetch 一样有"预检"?(设计如此,长连接不适合频繁预检;但服务端必须校验 Origin)
  • WebSocket 怎么带身份认证?(握手时带 Cookie 或在 query 带 token)

Q17:怎么实现"白名单允许多个源"?

考察点:CORS 实战配置

标准答案

Access-Control-Allow-Origin 不支持多个源,只能:

方案 1:动态返回(推荐)

js
const whitelist = ['https://a.com', 'https://b.com'];
app.use((req, res, next) => {
  const origin = req.headers.origin;
  if (whitelist.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
    res.setHeader('Vary', 'Origin');     // ⭐ 别忘了
  }
  next();
});

方案 2:cors 中间件

js
app.use(cors({
  origin: (origin, cb) => {
    if (!origin || whitelist.includes(origin)) cb(null, true);
    else cb(new Error('Not allowed'));
  },
}));

方案 3:用通配符(无凭证场景才能用)

http
Access-Control-Allow-Origin: *

Q18:fetch 默认 credentials 值是什么?背后什么考虑?

考察点:fetch API 细节

标准答案

fetch 默认 credentials: 'same-origin'

  • 同源请求 → 带 cookie
  • 跨源请求 → 不带 cookie

XMLHttpRequest 默认 withCredentials = false,行为与上面一致。

为什么这么设计

  • 默认安全:跨源不带凭证,避免误传敏感信息
  • 早期 fetch 默认是 'omit'(同源也不带),引发大量项目崩溃,Spec 在 2017 年改为 'same-origin' 与 XHR 对齐

应用建议

  • 公开 API:'omit'
  • 同源接口:用默认 'same-origin'
  • 跨源带 cookie:显式 'include'

考察点:协议细节、易混淆点

标准答案

默认不带,即使前端 credentials: 'include'预检请求 (OPTIONS) 也不带 Cookie。预检纯粹用来"探路",不应携带任何业务数据。

只有真请求才会带上 Cookie(前提是后端 Allow-Credentials: true)。

含义

  • 后端处理 OPTIONS 时不能依赖 Cookie(可能为 null)
  • 鉴权中间件应该跳过 OPTIONS
js
// 错误示例
app.use(authMiddleware);   // 这会让 OPTIONS 也被拦截,预检失败
app.use(cors());

// 正确示例
app.use(cors());                                  // 先处理 CORS
app.use((req, res, next) => {
  if (req.method === 'OPTIONS') return next();    // 跳过鉴权
  authMiddleware(req, res, next);
});

追问

  • 预检会带哪些头?(Origin、Access-Control-Request-Method/Headers,以及 User-Agent 等基础头)

Q20:CORS 和 CSP 有什么区别?

考察点:两个常被混淆的浏览器安全机制

标准答案

维度CORSCSP
全称Cross-Origin Resource SharingContent Security Policy
解决"我(服务端)允许谁来请求我""我(页面)允许加载/执行哪些资源"
谁配置API 服务端(响应头)页面服务端(响应头/meta)
限制方向跨源读取响应页面内加载执行的资源
典型头Access-Control-Allow-*Content-Security-Policy: default-src 'self'
防御目标让跨源 API 调用安全可控防 XSS、防资源被劫持

举例

  • 你想从 app.comapi.com:靠 CORS
  • 你想禁止 app.com 加载除自家以外的脚本:靠 CSP

追问

  • CSP 怎么防 XSS?(限制 inline script 和 unsafe eval)
  • CORS 错误能被 CSP 影响吗?(CSP 的 connect-src 也可能限制 fetch 目标,二者互不替代)

🎯 速记口诀

同源 = 协议+域名+端口;CORS 是服务端授权;简单请求直接发,复杂请求先 OPTIONS;带 Cookie 双方签字且不能用 *;开发用 proxy,生产用 Nginx;CORS 不防 CSRF。