前端跨域问题为什么总出现以及常见解决方案

chinaz
chinaz 正式会员超兽战士
发布于 2026-10-07 10:26 ·1 浏览 ·0 回复

跨域问题不是 Bug,而是浏览器同源策略的必然产物:只要页面的协议、域名、端口三者中任意一项与请求目标不同,浏览器就会拦截响应。最通用的解法是服务端配置 CORS 响应头;开发阶段用本地代理最省事,生产环境用 Nginx 反向代理把接口收进同一域名。

同源策略到底拦的是什么?

结论:拦截发生在浏览器端,不在服务端——请求往往已经发出、服务端也返回了 200,是浏览器把响应扣下不交给 JS。

同源的三要素是协议、域名、端口,https://a.com 和 http://a.com 不同源,https://a.com:443 和 https://a.com 算同源(默认端口可省略),http://localhost:3000 和 http://localhost:8080 也不同源。

这解释了一个经典现象:curl、Postman、后端日志里一切正常,只有浏览器控制台报 Access to XMLHttpRequest at ... has been blocked by CORS policy。排查跨域时先看 Network 面板——如果请求有响应体、状态码 200,就是响应头不合规;如果压根没发出实际请求,那是预检 OPTIONS 失败了。

为什么有的接口会多发一次 OPTIONS 请求?

结论:这不是重复请求,而是 CORS 预检(preflight),只有"非简单请求"才会触发。

满足全部三个条件的才算简单请求:方法是 GET、HEAD、POST 之一;请求头只包含 Accept、Accept-Language、Content-Language、Content-Type 等安全头;Content-Type 只能是 application/x-www-form-urlencoded、multipart/form-data、text/plain 三种。

只要用了 application/json、加了 Authorization 或自定义头(如 X-Token),浏览器就先发一个 OPTIONS 探路,服务端必须返回 Access-Control-Allow-Methods 和 Access-Control-Allow-Headers 才有后续。因此后端框架里 OPTIONS 请求返回 405、或网关把 OPTIONS 拦掉,都会直接导致跨域。

CORS 响应头怎么配才不踩坑?

结论:核心是 Access-Control-Allow-Origin,带 Cookie 时它不能写 *。

四条硬规则:

  1. Access-Control-Allow-Origin 必须精确匹配来源,如 https://a.com,或由服务端读取请求的 Origin 后按白名单反射。
  2. 带凭证请求需要三处对齐:服务端 Access-Control-Allow-Credentials: true,前端 xhr.withCredentials = true 或 fetch(url, { credentials: 'include' }),且 ACAO 不能是 *。
  3. 预检缓存用 Access-Control-Max-Age: 86400 减少 OPTIONS 次数,注意 Chromium 会把上限截断到 600 秒。
  4. Access-Control-Allow-Headers 和 Allow-Methods 要列出实际用到的值,不要指望 * 在带凭证场景生效。

另外,CORS 只能在你能改服务端代码时使用。调第三方接口、对方不加响应头,前端怎么改都没用。

改不了服务端怎么办?代理方案怎么做?

结论:开发环境用构建工具的 devServer 代理,生产环境用 Nginx 反向代理,两者本质都是让浏览器只跟同源地址通信。

Vite 的配置示例:

// vite.config.js
export default {
  server: {
    proxy: {
      '/api': {
        target: 'http://127.0.0.1:8080',
        changeOrigin: true,               // 把 Host 头改成目标域名
        rewrite: p => p.replace(/^\/api/, '')
      }
    }
  }
}

Webpack DevServer 对应的是 devServer.proxy,参数含义一致。changeOrigin: true 在目标服务按 Host 做虚拟主机或校验时是必须的。

生产环境用 Nginx:

location /api/ {
    proxy_pass http://127.0.0.1:8080/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
}

前端请求写成 /api/user,浏览器看到的是同源请求,跨域在浏览器层面根本不存在。

其它方案分别适合什么场景?

结论:JSONP 已基本淘汰,postMessage 解决的是窗口通信,WebSocket 不走同源策略。

  • JSONP 只支持 GET、靠 <script> 标签加载,无法处理 POST 和错误状态码,只应维护老代码时使用。
  • window.postMessage 用于 iframe 与父页面、多个窗口之间的跨域通信,接收方必须校验 event.origin。
  • WebSocket 握手不受同源策略限制,但服务端应在握手时校验 Origin 头,否则等于开放给任意站点。
  • 跨域携带 Cookie 时,还需注意 SameSite:Chrome 从 80 版本起把未声明的 Cookie 默认为 SameSite=Lax,跨站请求要显式设置 SameSite=None; Secure。

几个高频翻车点

代理配好了仍报跨域,通常是前端代码里还写死了 http://api.xxx.com 的完整地址,绕过了代理;或者 WebSocket 没在代理里开 ws: true。

服务端用反射 Origin 的方式做动态校验时,务必用白名单匹配,直接回显任意 Origin 等于把跨域防护关掉。预检失败的接口,优先检查 OPTIONS 是否被鉴权中间件提前拦截了。

一句话收束:跨域只能由服务端(或中间层代理)解决,前端改代码、加请求头都无效;开发用代理,生产用 Nginx,需要直连第三方接口时走 CORS 响应头。

版权声明:本文来自 GJ站长论坛《前端跨域问题为什么总出现以及常见解决方案》
原文链接:https://www.gj0.com/thread-231.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。
他们都看过 1 人浏览过
GJ论坛站长

全部回复 0

还没有回复,来抢沙发~