同源策略是什么
协议、域名、端口三者全相同才算同源。跨源的前端请求,浏览器会先判断是否放行,拦的是 JS 读响应,不是请求发不出去。
简单请求 vs 预检
- 简单请求:GET / 部分 POST、无自定义头,直接发,靠响应头
Access-Control-Allow-Origin决定能否读; - 预检:带自定义头或非简单方法时,浏览器先发
OPTIONS探路,通过才发真实请求。
常见错误配置
- 盲目返回 *:又带凭据就矛盾,且等于对任何源开放;
- 漏了预检头:只配了主请求,OPTIONS 被拒,真实请求根本到不了;
- 用代理代替 CORS:能解但把跨域搬到了服务端,仍要控制信任。
实战案例:三个真实报错
- 响应头重复:Nginx 与后端框架各加了一次
Access-Control-Allow-Origin,浏览器只接受一个值,报The 'Access-Control-Allow-Origin' header contains multiple values。用curl -I看响应头,只保留一处。 - OPTIONS 返回 405:预检请求被当成普通请求拒绝,真实请求根本发不出去。修复:让网关或框架对
OPTIONS直接返回 204,并回填允许的方法与头。 - 带 Cookie 就失败:跨域要同时满足前端
credentials: 'include'与后端Access-Control-Allow-Credentials: true,且Allow-Origin不能是*,必须回显具体来源。
常见问题(FAQ)
CORS 是后端漏洞吗?不是,它是浏览器强制的安全策略;后端返回的头只是“授权”,不放行则脚本读不到响应。为什么请求发出去了仍报错?因为拦的是“读响应”,请求本身已到达服务器——所以写操作仍要做鉴权与幂等。端口不同算同源吗?不算,协议、域名、端口任一不同即跨源。本地开发怎么处理?用开发服务器代理(如 Vite 的 server.proxy),比放宽 CORS 安全得多。返回 * 就够了吗?公开只读接口可以;涉及凭据或内部数据必须白名单校验来源。
CORS 与 CSRF 的关系
这两个概念经常被混为一谈,但方向完全不同:
- CORS 保护的是“读取”:它限制的是 A 站点能否读到 B 站点的响应内容,属于浏览器保护用户数据不被跨站读取的机制;
- CSRF 攻击的是“写入”:攻击者并不需要读取响应,只要让浏览器带上用户的 Cookie 发出请求即可,因此宽松的 CORS 并不是 CSRF 的成因;
- 因此两者的防御也不通用:CORS 无法防住 CSRF,必须依靠
SameSite、CSRF Token 或校验Origin。
与之相关的几个头
Access-Control-Allow-Origin只控制读取,不控制是否发出请求;Access-Control-Max-Age决定预检结果的缓存时长,太小会导致每个请求都先发一次 OPTIONS,明显增加延迟;Vary: Origin在使用动态回显来源时必不可少,否则 CDN 会把一个来源的响应缓存后发给另一个来源。
把这些配合好,既能开放必要的跨域能力,又不会把权限边界弄丢。
调试跨域问题的实操步骤
- 先区分请求是否真正发出:打开网络面板观察是否存在预检请求。若预检失败,真实请求根本不会发送,此时修改业务逻辑毫无意义。
- 完整查看响应头:跨域错误的原因往往写在响应头里,直接查看实际返回的头,比根据报错文字猜测更快。
- 确认来源是否被回显:使用凭据时来源必须精确匹配而不能使用通配,回显错误或缺少相应声明都会导致失败。
- 用命令行复现:用命令行工具手动带上来源头发起请求并观察响应,能排除浏览器缓存与前端代码的干扰,快速确认服务端行为。
- 区分开发与生产配置:开发环境常用宽松配置以便联调,务必确认生产配置是独立且更严格的,避免把调试配置带上线。
跨域问题的本质是配置问题而非代码错误,按上述顺序验证可以把排查范围快速缩小到具体的一个响应头。