如何解决前端跨域问题

如何解决前端跨域问题

只要你做过前后端分离,就几乎一定遇到过这个报错:

1
2
Access to XMLHttpRequest at 'https://api.xxx.com/...'
from origin 'https://web.xxx.com' has been blocked by CORS policy...

这就是经典的“跨域问题”。本文主要解决三个问题:

  • 什么是“同源策略”?什么是“跨域”?
  • 浏览器为什么要限制跨域?哪些操作会被限制?
  • 工程中有哪些常用的跨域解决方案?(CORS、代理、JSONP、postMessage 等)

一、什么是同源策略与跨域?

1. 同源策略(Same-Origin Policy)

同源:协议、域名、端口号三者都相同。

例如:

  • https://a.com:443/pagehttps://a.com:443/api → 同源
  • http://a.comhttps://a.com → 不同源(协议不同)
  • https://a.comhttps://b.com → 不同源(域名不同)
  • https://a.com:443https://a.com:8443 → 不同源(端口不同)

同源策略是浏览器的一个重要安全基石,它限制以下行为:

  • 读取非同源页面的 DOM
  • 获取非同源接口的响应内容(XHR/fetch)
  • 读写非同源的 Cookie、LocalStorage、IndexDB 等

注意:

  • 请求本身是会发出去的(网络上可以看到),但浏览器会拦截响应结果,不允许 JS 访问。

2. 什么算“跨域”?

只要发起的请求目标与当前页面的“源”不同,就是跨域请求。

常见场景:

  • 本地开发:http://localhost:3000http://localhost:8080
  • 前端部署在 web.xxx.com,后端在 api.xxx.com

二、浏览器的跨域限制具体表现在哪?

1. XHR / fetch

1
2
3
4
fetch("https://api.xxx.com/user")
.then((res) => res.json())
.then(console.log)
.catch(console.error);

如果目标域未正确设置 CORS 响应头,浏览器会:

  • 发起网络请求
  • 收到响应
  • 但在 JS 层抛出 CORS 错误,不允许访问响应体

2. 表单提交与图片/脚本等标签

注意:

  • <form> 提交、<img><script><link> 等标签本身不受同源策略限制(请求可以发,响应内容不会被 JS 直接访问)
  • <script> 加载跨域脚本有自己的安全约束(如 CSP、SRI)

三、CORS:最标准的跨域解决方案

CORS(Cross-Origin Resource Sharing) 是 W3C 标准,允许服务器通过响应头控制哪些源可以访问资源。

1. 简单请求 vs 预检请求

简单请求(Simple Request)

满足以下条件:

  • 方法是 GET / HEAD / POST
  • 自定义头受限(常见头:AcceptAccept-LanguageContent-LanguageContent-Type 仅限 text/plainmultipart/form-dataapplication/x-www-form-urlencoded

浏览器会直接发送请求,并根据响应头判定是否允许 JS 访问结果。

预检请求(Preflight)

如果不满足“简单请求”条件(如使用 PUT/DELETE、自定义头、application/json 等),浏览器会在正式请求前:

  • 先发一个 OPTIONS 请求作为“预检”
  • 服务端在预检响应中声明是否允许该跨域请求

2. 核心响应头

示例:

1
2
3
4
5
Access-Control-Allow-Origin: https://web.xxx.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With
Access-Control-Max-Age: 86400

含义:

  • Access-Control-Allow-Origin:允许访问的源
    • 不能为 * 的情况下再配合 Allow-Credentials: true
  • Access-Control-Allow-Credentials:是否允许携带 Cookie
  • Access-Control-Allow-Methods:允许的 HTTP 方法
  • Access-Control-Allow-Headers:允许的自定义请求头
  • Access-Control-Max-Age:预检结果的缓存时间(秒)

3. 前端 fetch 配置

1
2
3
4
5
6
fetch("https://api.xxx.com/user", {
method: "GET",
credentials: "include", // 携带 Cookie
})
.then((res) => res.json())
.then(console.log);

注意:

  • 如果 credentials: include,则服务端:
    • Access-Control-Allow-Origin 不能是 *,必须是具体域名
    • 必须加上 Access-Control-Allow-Credentials: true

四、开发环境常用:代理转发解决跨域

在本地开发时,通常不希望修改后端配置,可以通过“开发服务器代理”来规避跨域。

原理:

  • 浏览器 → 本地开发服务器(同源) → 代理转发到后端 API
  • 对浏览器来说,始终是同源请求

1. Vite 示例

1
2
3
4
5
6
7
8
9
10
11
12
// vite.config.js
export default {
server: {
proxy: {
"/api": {
target: "https://api.xxx.com",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
},
};

前端请求:

1
fetch("/api/user"); // 实际由 dev server 转发到 https://api.xxx.com/user

2. Webpack devServer 示例

1
2
3
4
5
6
7
8
9
devServer: {
proxy: {
"/api": {
target: "https://api.xxx.com",
changeOrigin: true,
pathRewrite: { "^/api": "" },
},
},
}

这种“代理转发”方案只适用于开发环境或自有网关服务,不适用于用户浏览器直接访问的线上环境。


五、JSONP(老方案,了解即可)

原理利用了 <script> 标签可以跨域加载脚本这一特性:

  1. 前端定义一个全局回调函数,如 window.handleData
  2. 在页面中插入 <script src="https://api.xxx.com/jsonp?callback=handleData">
  3. 服务器返回一段 JS 代码:handleData({ /* 数据 */ })

局限:

  • 只能用 GET 请求
  • 不安全(执行远程脚本)
  • 不方便处理错误与状态码

现代项目中已很少使用,除非与老系统兼容。


六、postMessage:跨窗口/iframe 通讯

当你的页面与其他源的页面通过 iframe 或新窗口协作时,可以使用 window.postMessage

1
2
3
4
5
6
7
8
// 子窗口/iframe 向父窗口发送
parent.postMessage({ type: "READY" }, "https://parent.com");

// 父窗口监听
window.addEventListener("message", (event) => {
if (event.origin !== "https://child.com") return;
console.log(event.data);
});

注意:

  • 始终校验 event.origin,避免接受来自恶意页面的消息。

postMessage 并不是解决 XHR/fetch 跨域问题的方案,而是解决页面间通信的方案。


七、Nginx 反向代理 + 同域部署

在生产环境中,常见做法是使用 Nginx 等网关:

  • 将前端静态资源与后端 API 部署在同一主域名下的不同路径
  • 通过 Nginx 的 location 转发到不同服务

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
server {
listen 80;
server_name example.com;

location / {
root /var/www/web; # 前端静态资源
try_files $uri /index.html;
}

location /api/ {
proxy_pass http://backend-service/;
}
}

对浏览器而言,请求都来自 https://example.com,不存在跨域问题。


八、不同场景下的方案选择

场景 推荐方案
正式环境,前后端分离,域名不同 首选 CORS,后端统一配置;或通过反向代理统一为同域
开发环境,本地端口不同 开发服务器 代理转发(Vite/Webpack devServer)
老接口,仅支持 JSONP 使用 JSONP 封装,或在服务端增加 CORS 支持
跨域页面通信(iframe/新窗口) window.postMessage

九、总结

“跨域问题”本质上是浏览器的同源策略在起作用,目的是保护用户安全,而不是“为难前端”:

  • 请求可以发送,但响应内容是否可被 JS 访问由浏览器控制
  • CORS 是标准且现代的解决方案,核心在于服务器通过响应头显式声明允许哪些源访问
  • 开发环境中,我们可以用代理转发来规避跨域限制,加快开发效率

真正理解同源策略与 CORS 的原理后,面对各类“跨域错误”就不再是靠“百度配置复制粘贴”,而是能根据项目架构、环境与安全要求,选择最合适的解决方案。


如何解决前端跨域问题
https://sunjc.vip/2025/01/08/前端跨域问题与解决方案/
作者
Sunjc
发布于
2025年1月8日
许可协议