Skip to content

如何解决跨域问题

什么是跨域

跨域全称是跨域资源共享(Cross-Origin Resources Sharing, CORS),它是浏览器的保护机制,只允许网页请求同一域名下的服务,同一域名的要求是,协议、域名和端口都要保持一致:

bash
http://domain.com -> http://domain.com/api/user

如果有一项不同,那么就是跨域请求:

bash
http://domain.com:3000 -> http://domain.com:5000/api/user http://domain.com ->
https://domain.com/api/user http://domain.com -> http://otherdomain.com/api/user

在前后端分离的项目中,解决跨域可以从 3 种情况下手:

  • 配置后端
  • 配置前端
  • 配置产品环境服务器

对于传统使用 jsonp 的形式,由于不适合 react 或 vue 之类的开发,并且也有安全隐患,这里就不作介绍了。

方法 1: 配置前端

浏览器是否启用跨域保护机制是根据后端的响应来定的,所以配置后端是最直接的一种方法,浏览器根据的是响应的Access-Control-Allow-Origin 响应头来决定的:如果这个字段的值是"*",那么会允许所有请求,如果是一个域名,那么浏览器就不会对这个域名下的请求进行跨域保护:

bash
Access-Control-Allow-Origin: http://localhost:3000

根据后端程序的语言和库的不同,配置 Access-Control-Allow-Origin 响应头的方法也不同,如果使用的是 node.js 和 express,那么可以添加 cors 中间件:

javascript
const cors = require('cors');

app.use(cors());

cors 默认会允许所有跨域请求,如果要限制指定域名,可以给它的 origin 配置项传递一个字符串或数组,用于指定一个或多个允许跨域的域名:

javascript
app.use(cors({ origin: ['http://domain1.com', 'http://domain2.com'] }));

方法 2:配置前端

第 2 种方法,是在前端开发环境中,配置代理,中转请求,因为跨域是浏览器的保护机制,如果脱离的浏览器发送请求,是不会受到跨域保护机制的影响的,所以我们可以使用一个中转服务器来发送和接收请求,前端只需要请求这个中转服务器,并保持和中转服务器的 URL 保持一致即可。​

一般的前端脚手架工具都支持配置代理,例如 vite、create react app 等,这里以 vite 为例, vite 自带的开发服务器支持设置代理,在 vite.config.js 文件里,我们可以配置 server 配置项下的 proxy 配置,把对 /api 路径的请求代理转发到真实的后端服务路径,再根据需要,对转发的 URL 进行改写。

javascript
server: {
  proxy: {
    "/api": {
      target: "http://localhost:5000",
      changeOrigin: true,
      rewrite: (path) => path.replace(/^\/api/, ""),
    },
  },
},

这里的配置项把前端请求的 /api 路径转发到 http://localhost:5000/,并把 /api 这一段删除,因为这里后端提供的 API 中不带 /api 这一段:

bash
/api -> http://localhost:5000 /api/user -> http://localhost:5000/user

这样在开发的时候,就避免了跨域问题。前端应用中可以直接请求 proxy 中配置的 /api 来请求后端服务,不用再写前边的协议、域名和端口了,因为默认是和前端相同的:

bash
fetch("/api");

这个时候,如果去掉后端的跨域支持,前端仍然能请求后端的数据:

bash
// const cors = require("cors");

// app.use(cors());

方法 3:产品环境

对于产品环境,或者开发服务器不支持代理的情况下,我们可以自己手动创建一个中转服务器来代理请求。 假设我们打包好的前端项目放到了 dist 目录下,入口文件为 index.html:

shell
dist ├──  index.html

我们可以使用 express 来创建一个服务器,负责发送前端页面,并代理请求。例如我们这里把让请求都返回 dist 下的 index.html 文件:

javascript
app.use(express.static("dist"));

// serve index.html for all routes
app.get("\*", (req, res) => {
  res.sendFile(\_\_dirname + "/dist/index.html");
});

但是对于 /api 这个路径,我们还是需要配置代理,这里需要安装 http-proxy-middleware 依赖库,它和 vite 底层使用的代理服务是一样的,配置也几乎一样,不同的是 vite 中 rewrite 配置项这里叫作 pathRewrite:

javascript
const { createProxyMiddleware } = require('http-proxy-middleware');

app.use(
  '/api',
  createProxyMiddleware({
    target: 'http://localhost:5000',
    changeOrigin: true,
    // 这里是 pathRewrite,跟 vite 不太一样
    pathRewrite: (path) => path.replace(/^\/api/, '')
  })
);

这样,我们再启动这个服务器来负责展现前端产品环境的页面,可以看到,仍然可以成功的请求的后端的数据。

bash
node server_production.js

除了使用 express,也可以使用 nginx 等专业的服务器程序,你可以参考相关文档来配置代理。