跨域问题解决方法汇总
跨域问题解决方法汇总
写代码写到半夜,浏览器突然弹出一个红字报错:“Access to XMLHttpRequest at ‘xxx’ from origin ‘xxx’ has been blocked by CORS policy。”那一刻,很多前端大概都有想把显示器推下桌的冲动。明明接口在 Postman 里跑得好好的,一到页面里就“跨域被拦截”。其实跨域不是什么高深莫测的黑魔法,它只是浏览器出于安全考虑立的一条规矩:默认情况下,A 网站不能随便拿 B 域名的资源。你要是没提前打个招呼,浏览器就当坏人挡在前面。可现实里,谁家项目能永远只跑在一个域名下?小程序、APP、H5、后台系统,哪一个都要互相“串门”。不把跨域这事捋清楚,上线前的最后一晚注定要通宵。

我第一次被跨域“坑”,是在一个活动页里调内部接口。本地开发时用了个代理,跑得飞快。一打包上线,域名换成客户给的外链,接口直接“血条清零”。控制台里满屏红字,同事在群里甩截图:“是不是后端没开权限?”后端也很委屈:“我开了啊,你看我返回了数据。”其实返回归返回,浏览器不认账。因为跨域不是“有没有数据”的问题,而是“能不能让前端 JavaScript 拿到”的问题。就像你去便利店买东西,老板愿意卖,但门口保安说“你没带会员卡,不能进”。这时候你骂老板没用,得找办法让保安放行,或者换条不用过保安的路。
很多前端一开始会想“能不能关掉浏览器安全检查”。确实,启动 Chrome 加个参数,或者关掉同源策略,本地是能跑了。可你总不能要求用户也这么干吧?生产环境更不可能靠“关保安”来解决问题。跨域的本质不是技术问题,而是信任和规则的谈判。你得让服务器告诉浏览器:“这请求是安全的,我认。”浏览器才会松口。所以解决问题的方向,基本都围绕一句话:怎么让服务器开口背书,或者怎么绕过浏览器的拦截规则。
在实际项目里,跨域场景千奇百怪。有的接口只是简单地读个配置,有的要传 Cookie 做登录态,还有的要上传大文件带自定义头。不同的场景,适合的解法不一样。选错了方案,轻则上线后接口时好时坏,重则把安全策略搞成一锅粥。更麻烦的是,有些跨域问题在开发时不出现,一上预发就冒出来。因为开发用 localhost,预发用二级域名,看起来差不多,浏览器眼里却是“两个陌生人”。这时候再临时改配置,往往会牵一发而动全身。所以与其每次临时抱佛脚,不如提前把常见的解法摸清楚,像工具箱一样备好,遇到不同情况随手拿出最合适的那把。
从后端开口:最稳妥的“通行证”
说到跨域,CORS(跨域资源共享)几乎是绕不开的词。它不是新技术,却是最正统的解法。原理很简单:后端在返回头里加几个字段,告诉浏览器“我允许谁、用什么方式、能不能带凭证”。比如 Access-Control-Allow-Origin: https://a.com,就是明确放行指定来源。浏览器看到这个头,才会放行请求。对前端来说,这几乎是无感的,只要后端配好,代码不用动。
但实际落地时,坑往往不在“能不能配”,而在“怎么配才不出错”。最常见的是用通配符 *,看起来省事,却不支持携带 Cookie。一旦接口需要登录态,通配符直接让浏览器把凭证扔掉。更隐蔽的问题出现在预检请求(preflight)。当你用了自定义头,或者 Content-Type 不是简单的几种,浏览器会先发一个 OPTIONS 请求“探路”。如果后端没处理 OPTIONS,或者返回头没配对,正式请求根本发不出去。很多时候,前端盯着控制台看,以为是请求没发出去,其实是请求被 OPTIONS 卡在半路。
还有一种情况是“多个来源怎么放行”。有些项目既要给官网用,又要给小程序用,还得给合作方用。这时候硬编码一个域名肯定不够。通常的做法是在后端判断来源,动态写入允许的域名。但这也带来风险:如果判断逻辑太宽松,等于把大门敞开。稳妥的做法是维护一份“信任名单”,只放行已知来源。再加上允许的方法和头的白名单,尽量别用 * 全部放行。CORS 像一把钥匙,既要开得对,也得知道哪扇门不该开。
反向代理:把“跨域”变成“同域”
如果让后端改成本太高,或者你压根没权限动服务器,反向代理就是很讨喜的解法。思路其实很朴素:浏览器只认域名,我把请求先发到我自己的服务器,再由服务器去目标接口拿数据。因为前后端同域,浏览器不拦;服务器之间通信没有跨域一说。这样既不用改后端,也不用让用户折腾浏览器。
在开发阶段,Webpack Dev Server 的 proxy 配置是很多人的救星。配好 target 和路径重写,本地跑起来就像接口天生在同域一样。线上环境也可以用 Nginx 做类似的事。比如把 /api 开头的请求都转发到另一个服务,对外只暴露一个主域名。这样不仅解决跨域,还能统一做缓存、限流和安全拦截。不过反向代理也不是“银弹”。它多了一跳,延迟会略微增加;配置写错,还可能把不该转发的路径也转走。
更需要注意的一点是 Cookie 和登录态。如果代理只是简单转发,而目标接口依赖 Cookie 里的会话信息,你得确保 Cookie 跟着请求一起过去。有时候还得处理 SameSite 和 Secure 的问题,尤其是 HTTPS 环境下。反向代理更像“把跨域藏在幕后”,问题没消失,只是被转移到了你能控制的地方。只要转发规则写得干净,日志盯得紧,它往往是上线前最稳的兜底方案。
JSONP:老项目的“祖传手艺”
提到跨域,很多人还会想起 JSONP。它不需要 CORS,不需要 OPTIONS,甚至不需要 XMLHttpRequest,只靠 <script> 标签的 src 去加载资源。因为浏览器对 script 的跨域限制很松,JSONP 在早年几乎是“万能钥匙”。它的核心是利用回调函数:前端定义一个函数,后端返回一段 JS,直接调用这个函数把数据送回来。
现在看 JSONP,会觉得它很原始。没有错误处理,只能 GET,还得信任对方返回的代码。可在老项目里,它依然是能跑的解法。比如一些第三方统计、天气接口,至今只支持 JSONP。这时候你骂它过时没用,不如老老实实用它。但要注意安全:JSONP 等于把执行权交给了对方,万一对方返回恶意代码,你的页面就可能被劫持。能不用就不用,非要用时,至少要对来源做严格限制。
JSONP 的另一个问题是“命名冲突”。如果多个地方同时用,很容易把全局函数踩踏。现代项目通常会用动态生成函数名、加载完成后立刻清理的方式来规避。但终究是补丁摞补丁。JSONP 像老房子里的木楼梯,能上人,但一踩吱呀响。能换楼梯最好,不能换时,至少得知道哪块板子松了。
PostMessage 和窗口桥接:页面之间的“悄悄话”
不是所有跨域都发生在接口请求里。有时候是页面和页面之间、页面和 iframe 之间需要通信。比如主站嵌入第三方支付页,或者跨域弹窗要回传数据。这时候 CORS 帮不上忙,因为资源本身没问题,只是两个窗口想“说句话”。PostMessage 就是为此而生的。
它的用法很直白:一方用 postMessage 发消息,另一方监听 message 事件。关键在于验证来源。收到消息时,一定要检查 event.origin,确认是你信任的域名。否则任何一个恶意页面都能往你的窗口塞消息,搞不好还能诱导操作。结合约定的数据结构,再加一层简单的签名或时间戳,跨域通信就能变得可控。
不过 PostMessage 是“异步”的,消息可能延迟、重复、甚至丢失。复杂的场景下,你还需要设计请求-响应的配对机制,像个小协议一样跑在消息之上。它解决不了接口跨域,却能打通页面之间的隔阂。很多时候,跨域问题不是单一技术能包治百病的,而是需要把不同方案像拼图一样组合起来。
结语:跨域不是敌人,而是规则的边界
写到这里,你会发现跨域问题很少有“唯一正确”的答案。CORS 靠后端背书,反向代理靠转发掩护,JSONP 靠老办法硬上,PostMessage 靠窗口协作。每一种解法都有它的适用场景,也都有它的副作用。真正的难点不在于记住这些名字,而在于判断什么时候该用哪一把钥匙。
更重要的是,跨域提醒我们一个事实:安全与便利永远在拔河。浏览器越拦得紧,用户就越不方便;开得越松,风险就越高。你在解决跨域的时候,其实是在做一次权衡:哪些数据可以公开,哪些操作需要凭证,哪些场景可以妥协,哪些底线不能碰。
把跨域当成项目里的一次“边界谈判”,而不是“技术故障”,心态会平和很多。提前沟通、提前验证、提前留好退路。很多时候,最稳妥的方案不是最炫技的,而是最清楚自己边界在哪里的那一套。跨域问题解决之后,你会发现,真正被修复的,不只是浏览器里那行红字,而是整个协作流程里的信任裂缝。而这一点,比任何一行配置都值钱。