一、同源策略在防什么
源 = 协议 + 域名 + 端口,三者全同才算同源。https://a.com 与 http://a.com 不同源,a.com:443 与 a.com:8443 也不同源。
同源策略限制的是读取响应,不是「发请求」。跨域的 POST 请求往往已经到达服务器并被执行了,只是浏览器不把响应交给 JS。所以「跨域请求没生效」的判断要谨慎——去看服务端日志,可能已经写库了。
二、简单请求 vs 预检请求
满足以下全部条件的是简单请求,直接发出,不预检:
- 方法是
GET/HEAD/POST - 请求头只含安全列表内的(
Accept、Accept-Language、Content-Language、Content-Type的部分值) Content-Type是text/plain、multipart/form-data、application/x-www-form-urlencoded之一
其余情况(PUT/DELETE、Content-Type: application/json、自定义头如 Authorization)都会先发一个 OPTIONS 预检,服务端必须正确响应,浏览器才会发真实请求。
这是很多人踩的坑:POST + application/json 不是简单请求,会触发预检;而 Nginx 没配 OPTIONS 直接返回 405,前端就报跨域。
三、响应头怎么配
Access-Control-Allow-Origin: https://www.it586.cn
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
规则:
Allow-Origin只能是一个具体源,或*。要带 Cookie 时必须是具体源,*会被浏览器拒绝。- 需要多域名时,服务端按请求的
Origin做白名单匹配,动态回填,不要直接回显任意 Origin(等于放开)。 Allow-Headers要包含实际发送的自定义头,大小写不敏感但必须列全。Max-Age让浏览器缓存预检结果,减少 OPTIONS 请求。
四、带 Cookie 的三件套
fetch(url, { credentials: 'include' })
Access-Control-Allow-Origin: https://www.it586.cn
Access-Control-Allow-Credentials: true
同时前端 Cookie 要设 SameSite=None; Secure(跨站携带 Cookie 的前提,且必须 HTTPS)。三者缺一不可,缺任何一个浏览器都会静默丢弃 Cookie。
另外 Allow-Origin 此时**不能是 ***,也不能是「先回 * 再回具体源」——响应头只能有一个值。
五、Nginx 配置示例
location /api/ {
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin $http_origin 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-Allow-Credentials true 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;
}
always 很重要:不加它,4xx/5xx 响应不会带 CORS 头,前端只能看到「跨域错误」,看不到真正的 500。
六、排查顺序
- 看 Network 里有没有 OPTIONS 请求,状态码是多少(405/404 说明服务端没处理预检)。
- 看响应头
Allow-Origin是否与页面源完全一致(含协议、端口,末尾不能有斜杠)。 - 带 Cookie 时确认
Allow-Credentials: true且 Origin 不是*。 - 检查是否有多个 CORS 头重复(Nginx 和 应用各加一次,浏览器会拒绝)。
- 确认请求真的跨域——很多时候是写错了协议或端口。
七、什么时候不该用 CORS
开发阶段更简单的方式是代理:前端 dev server 把 /api 代理到后端,浏览器看到的是同源请求,完全不涉及 CORS。生产环境如果前后端同域部署,也就不需要 CORS。只有「前端独立域名 + 后端 API 域名」才必须配。
小结
CORS 是浏览器行为,服务端只是「声明允许谁读」。JSON 请求必预检、OPTIONS 要返回 2xx、带 Cookie 不能用 *、always 让错误响应也带头。开发期用代理比调 CORS 省事。