Skip to content

工作扫盲之跨域问题

约 1745 字大约 6 分钟

2025-10-07

今天和后端联调遇到跨域问题,明明接口在 Postman 里能通,到浏览器就报错CORS。晚上无聊遂了解一下跨域问题并作记录。

OK Let's Dive in!🤿

核心概念

其实跨域不是 “bug”,是浏览器的 “安全洁癖” 导致的。先把两个基础概念掰明白,后面看解决方案就顺理成章了。​

1. 什么是 “同源”?—— 跨域的判断标准

浏览器判断 “要不要拦截这个请求”,全看 “请求来源” 和 “请求目标” 是不是 “同源”。

“同源” 有三个硬性标准,必须完全一致

  • 协议:比如 httphttps 算不同(我之前试过用 http 调后端的 https 接口,直接跨域);

  • 域名:比如 www.abc.comapi.abc.com 算不同(子域名不一样也不行),abc.comdef.com 更不用说;

  • 端口:比如 localhost:8080(前端本地)和 localhost:3000(后端本地)算不同(这是联调最常遇到的情况!)。

前端地址后端接口地址是否跨域原因
http://localhost:8080http://localhost:3000端口不同
https://www.abc.comhttp://www.abc.com协议不同
http://www.abc.comhttp://api.abc.com子域名不同
http://www.abc.com:80http://www.abc.com80 端口是 http 默认值

2. 为什么会有 “跨域拦截”?—— 浏览器的同源策略

这里要澄清一个关键误区:跨域时,请求其实发出去了,后端也会返回响应,但浏览器会 “半路拦截” —— 因为浏览器有个 “同源策略” 的安全机制,怕你被钓鱼。

举个例子:你刚在银行网站(https://bank.com)登录,Cookie 里存了你的登录信息;这时你不小心打开一个钓鱼网站(https://fake.com),如果没有同源策略,钓鱼网站的 JS 就能偷偷调用银行的接口,用你的 Cookie 获取账户信息 —— 这就出事了!

所以同源策略的本质是:保护用户数据安全,防止恶意网站通过 JS 窃取其他网站的资源(比如 Cookie、LocalStorage)

而 Postman 能通,是因为 Postman 不是浏览器,没有同源策略的限制 —— 它不管 “来源”,只要接口能通就返回数据,这也是我们联调时 “明明接口好的,前端却报错” 的核心原因。

解决方案

搞懂原理后,解决方案就很清晰了:前端能做的是 “绕开拦截”,但只能临时用;后端做的是 “告诉浏览器允许访问”,能彻底解决问题。

1. 前端解决方案

这些方案大多是 “治标不治本”,生产环境基本没用,但开发时能帮我们快速联调。

原理很简单:前端不直接调后端接口,而是先把请求发给 “本地代理服务器”(比如 Vue/React 脚手架自带的代理),再由代理服务器转发给后端 —— 因为代理服务器是 “服务器之间的通信”,不受浏览器同源策略限制。

举个 Vue 项目的例子(React 类似):

vue.config.js 里加一段配置:

module.exports = {
  devServer: {
    proxy: {
      // 匹配所有以 /api 开头的请求
      '/api': {
        target: 'http://localhost:3000', // 后端接口地址
        changeOrigin: true, // 允许跨域(关键)
        pathRewrite: {
          '^/api': '' // 比如前端发 /api/user,实际转发到 http://localhost:3000/user
        }
      }
    }
  }
}

配置完重启项目,前端直接调 /api/user 就行,不用写完整的后端地址 —— 亲测有效,我今天联调就是这么临时解决的。

注意:这个方案只适用于开发环境!打包上线后,前端没有脚手架的代理了,还是会跨域,得靠后端。

(2)iframe + postMessage:跨域传递数据(特殊场景)

如果需要在两个跨域的页面之间传递数据(比如父页面和 iframe 子页面),可以用 postMessage

比如父页面(http://a.com)给子页面(http://b.com)发消息:

// 父页面:获取iframe元素,发送消息
const iframe = document.getElementById('myIframe');
iframe.onload = function() {
  // 参数1:要发的数据,参数2:目标域名(\*表示所有域名,生产环境建议写具体域名)
  iframe.contentWindow.postMessage('我是父页面的消息', 'http://b.com');
};

// 父页面接收子页面的消息
window.addEventListener('message', function(e) {
  // 验证来源,防止恶意消息
  if (e.origin === 'http://b.com') {
    console.log('父页面收到子页面消息:', e.data);
  }
});

子页面(http://b.com)接收并回复:

// 子页面接收父页面消息
window.addEventListener('message', function(e) {
  // 验证来源,防止恶意消息
  if (e.origin === 'http://a.com') {
    console.log('子页面收到父页面消息:', e.data);
    // 子页面给父页面回复
    e.source.postMessage('我收到消息啦!', 'http://a.com');
  }
});

这个方案主要用于 “页面间通信”,不是调接口,属于特殊场景补充。

2. 后端解决方案

前端方案只是 “临时方案”,要让线上项目不跨域,必须靠后端配置 —— 核心是告诉浏览器:“这个前端域名是我允许的,你别拦截它的请求。”

(1)配置 CORS:最推荐,支持所有请求方式

CORS(跨域资源共享)是官方标准,原理是后端在响应头里加一些 “允许跨域” 的字段,浏览器看到这些字段就会放行。

重点配置的响应头:

  • Access-Control-Allow-Origin:允许访问的前端域名(比如 http://localhost:8080,生产环境写线上域名,不能随便用 *);

  • Access-Control-Allow-Methods:允许的请求方式(GET、POST、PUT、DELETE 等);

  • Access-Control-Allow-Credentials:是否允许带 Cookie(如果前端要传 Cookie,这个必须设为 true);

  • Access-Control-Allow-Headers:允许的自定义请求头(比如前端传 token 时,要加这个配置)。

(2)服务端代理:后端转发请求(特殊场景)

如果前端要调用 “不支持 CORS” 的第三方接口(比如某些老的公共 API),可以让后端帮忙转发:前端调自己的后端,自己的后端再调第三方接口,最后把数据返回给前端。

比如 Node.js(Express)转发示例:

const express = require('express');
const axios = require('axios');
const app = express();
// 前端调这个接口,后端转发到第三方API
app.get('/api/third-party', async (req, res) => {
  try {
    // 后端调第三方接口(不受同源策略限制)
    const response = await axios.get('https://third-party-api.com/data');
    // 把第三方的数据返回给前端
    res.send(response.data);
  } catch (error) {
    res.status(500).send('转发失败');
  }
});
app.listen(3000, () => {
  console.log('后端服务启动在3000端口');
});

这个方案的好处是:前端不用管跨域,第三方接口的密钥也能存在后端(避免暴露在前端),安全性更高。

总结:一句话搞定跨域

  • 开发环境:前端用 “代理”(Vue/React 脚手架配置)快速联调;

  • 生产环境:后端配 “CORS”(核心是 Access-Control-Allow-Origin 设对域名);

  • 特殊场景:服务端代理(第三方接口)。

其实跨域不难,关键是搞懂 “浏览器为什么拦截”,再针对性解决 —— 下次再遇到,应该不会再卡半小时了😂。

————————到底啦!————————