浏览器 Web 安全基础系列
CORS 到底在限制什么(当前篇)
前端页面从 http://localhost:3000 请求 http://localhost:4000/api,控制台出现 CORS 错误。很多人因此认为“请求没有发出去”。实际上,CORS 主要控制浏览器是否把跨源响应交给 JavaScript,部分请求可能已经到达服务器并执行。

CORS 是服务器给浏览器的许可
前端发起:
fetch('https://api.example.com/users');
浏览器在请求中带上:
Origin: https://www.example.com
服务端允许该来源时返回:
Access-Control-Allow-Origin: https://www.example.com
浏览器看到允许头,才把响应正文交给 JavaScript。curl 和后端服务不会执行这个检查,因为 CORS 是浏览器安全模型的一部分。
简单请求
某些满足条件的 GET、HEAD、POST 请求可以直接发送,不先预检。例如普通表单格式 POST:
fetch('https://api.example.com/search', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'q=cors'
});
“简单”不表示安全,也不表示服务端不用鉴权。它只是 Fetch/CORS 规范对请求形态的分类。
预检请求
如果使用 DELETE、自定义请求头或不属于简单类型的 Content-Type,浏览器通常先发 OPTIONS:
OPTIONS /users/1
Origin: https://www.example.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: authorization
服务端回答:
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Allow-Headers: Authorization
允许后,浏览器再发送真正请求。

Express 手写示例:
const allowedOrigins = new Set(['https://www.example.com']);
app.use((req, res, next) => {
const origin = req.headers.origin;
if (origin && allowedOrigins.has(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization');
}
if (req.method === 'OPTIONS') return res.sendStatus(204);
next();
});
Vary: Origin 用来提醒缓存:响应会因 Origin 不同而不同,避免代理错误复用 CORS 响应。
带 Cookie 的跨源请求
前端必须明确允许凭证:
fetch('https://api.example.com/profile', {
credentials: 'include'
});
服务端返回:
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Credentials: true
此时不能使用:
Access-Control-Allow-Origin: *
因为允许任意来源同时读取带凭证响应会破坏安全边界。
还要注意:credentials: include 只表示 Fetch 可以使用凭证,Cookie 是否真正发送仍要经过 Domain、Secure、SameSite 和第三方 Cookie 策略判断。
为什么 CORS 不能防 CSRF
CSRF 攻击通常不需要读取响应,只需要让状态修改请求到达服务器。HTML 表单可直接跨源提交:
<form action="https://bank.example/transfer" method="post">
<input name="to" value="attacker">
<input name="amount" value="1000">
</form>
即使银行没有给攻击网站配置 CORS,表单请求仍可能发送。攻击页面读不到返回页面,但转账已经执行。
因此:
CORS:谁能通过浏览器 JavaScript 读取响应
CSRF 防护:谁有资格让服务端执行状态修改
CORS 不是权限系统
把攻击者 Origin 从白名单移除,不能替代登录和资源授权。攻击者可以用自己的服务器、curl 或脚本直接请求 API,根本不受浏览器 CORS 约束。
每个接口仍需验证:
身份认证;
用户是否有权访问目标资源;
输入是否有效;
状态修改是否需要 CSRF 防护;
请求频率是否合理。
常见错误
反射任意 Origin
危险配置:
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
它等于允许任意网站读取带用户凭证的响应。
白名单使用 endsWith
origin.endsWith('example.com')
可能错误接受 notexample.com。应解析 URL 后精确匹配完整 Origin。
所有接口都允许所有方法和请求头
按业务实际需要最小化开放范围,尤其是凭证请求。
小结
CORS 是浏览器跨源读取许可,不是防火墙,也不是身份认证。理解“请求可能已发送,但响应不交给 JavaScript”,就能区分 CORS、同源策略和 CSRF。
参考资料
CORS 到底在限制什么:跨源请求、预检与凭证
https://lautung.com/archives/web-security-05-cors
评论