主题
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:80和http://a.com同源吗?(同源,80 是 http 默认端口)http://a.com和http://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 协议核心细节
标准答案:
简单请求:同时满足三个条件
- 方法是
GET/HEAD/POST Content-Type是text/plain/multipart/form-data/application/x-www-form-urlencoded- 没有自定义请求头
→ 浏览器直接发请求,看响应头 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)
Q5:跨域请求怎么携带 Cookie?
考察点: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 防御:
- Cookie 加
SameSite=Lax/Strict(核心防线,现代浏览器默认 Lax) - CSRF Token(一次性令牌,存在 form/header 里)
- 校验 Referer / Origin(次要手段)
- 关键操作二次验证(输入密码 / 短信验证码)
对比:
| 维度 | CORS | CSRF 防护 |
|---|---|---|
| 解决问题 | 浏览器能不能读跨源响应 | 攻击者能不能伪造已登录用户的请求 |
| 谁主导 | 浏览器 + 服务端 | 服务端 + 浏览器(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: OriginCDN 看到 Vary: Origin 后,会按不同 Origin 分别缓存。
追问:
Allow-Origin: *还需要 Vary 吗?(不需要,因为响应不随 Origin 变)
Q12:CORS 失败的请求,前端怎么调试?
考察点:实战调试能力
标准答案:
调试步骤:
- 打开 Chrome DevTools → Network
- 过滤 OPTIONS 请求,看预检响应:
- 状态码是不是 2xx?
- 是否含
Access-Control-Allow-Origin? Allow-MethodsAllow-Headers是否覆盖了你要用的?
- 看真请求响应头,是否有
Allow-Origin且匹配 - 看 Console 红字提示,里面写得很清楚缺什么
- 用 curl 模拟,确认后端到底返回什么:bash
curl -I -X OPTIONS https://api.com/users \ -H "Origin: https://app.com" \ -H "Access-Control-Request-Method: PUT" - 对比同源调用:用 Postman 调一下,能成说明不是接口问题,是 CORS 配置问题
常见错误对应:
| 控制台错误 | 原因 |
|---|---|
| No 'Access-Control-Allow-Origin' | 后端没设 |
| Allow-Origin: '*' but credentials | 带 cookie 不能用 * |
| Method PUT is not allowed | Allow-Methods 缺 PUT |
| Header X-Token is not allowed | Allow-Headers 缺 X-Token |
| Request requires preflight, which is disallowed | OPTIONS 被服务端拒了 |
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);
});安全要点:
- 目标 origin 不要写
'*',否则任何监听者都能收到 - 接收时必须校验
e.origin,避免恶意网站发假消息 - 传输的数据要做类型校验,别直接 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'
Q19:预检请求会带 Cookie 吗?
考察点:协议细节、易混淆点
标准答案:
默认不带,即使前端 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 有什么区别?
考察点:两个常被混淆的浏览器安全机制
标准答案:
| 维度 | CORS | CSP |
|---|---|---|
| 全称 | Cross-Origin Resource Sharing | Content Security Policy |
| 解决 | "我(服务端)允许谁来请求我" | "我(页面)允许加载/执行哪些资源" |
| 谁配置 | API 服务端(响应头) | 页面服务端(响应头/meta) |
| 限制方向 | 跨源读取响应 | 页面内加载执行的资源 |
| 典型头 | Access-Control-Allow-* | Content-Security-Policy: default-src 'self' |
| 防御目标 | 让跨源 API 调用安全可控 | 防 XSS、防资源被劫持 |
举例:
- 你想从
app.com调api.com:靠 CORS - 你想禁止
app.com加载除自家以外的脚本:靠 CSP
追问:
- CSP 怎么防 XSS?(限制 inline script 和 unsafe eval)
- CORS 错误能被 CSP 影响吗?(CSP 的
connect-src也可能限制 fetch 目标,二者互不替代)
🎯 速记口诀
同源 = 协议+域名+端口;CORS 是服务端授权;简单请求直接发,复杂请求先 OPTIONS;带 Cookie 双方签字且不能用 *;开发用 proxy,生产用 Nginx;CORS 不防 CSRF。