前端跨域问题为什么总出现以及常见解决方案
跨域问题不是 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 时它不能写 *。
四条硬规则:
Access-Control-Allow-Origin必须精确匹配来源,如https://a.com,或由服务端读取请求的Origin后按白名单反射。- 带凭证请求需要三处对齐:服务端
Access-Control-Allow-Credentials: true,前端xhr.withCredentials = true或fetch(url, { credentials: 'include' }),且 ACAO 不能是*。 - 预检缓存用
Access-Control-Max-Age: 86400减少 OPTIONS 次数,注意 Chromium 会把上限截断到 600 秒。 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 响应头。
原文链接:https://www.gj0.com/thread-231.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。