为什么会跨域

跨域问题在非前后端分离项目中是不存在的,因为所有请求(包括静态页面)都来自后端。而在前后端分离项目中,前端可能有自己的静态页面服务器,发送给后端的请求未必与前端页面 URL 同源。此时浏览器会默认阻止前端页面收到响应,并报告一个 CORS 错误。

两种情况

针对前后端分离,可能出现两种情况:

  1. 前后端项目分离,URL 不分离。
  2. 前后端部署时 URL 也分离。

对于第一种情况,仅在开发时会遇到跨域问题,部署环境实际是同源的;对于第二种情况,部署的时候也存在跨域问题。

开发时的跨域解决

如果有专门的开发用后端服务器,配套了 mook 数据之类的,那么直接给 mook 服务器配置响应头Access-Control-Allow-Origin: *就可以了,前端无需任何配置,也不会影响线上环境安全性。

如果因为某些原因,后台没法配置允许跨域,则前端需要配置一个本地的代理,绕过浏览器的跨域检测。

本地跨域代理

通过使用 nodejs 可以方便的构建一个跨域代理,并且给返回头加上Access-Control-Allow-Origin: *

const proxy = require('http-proxy-middleware')
const express = require('express')
let app = express()

app.use(
  '/',
  proxy({
    target: 'http://test.com',
    changeOrigin: true,
    onProxyRes: function (proxyRes, req, res) {
      proxyRes.headers['Access-Control-Allow-Origin'] = '*';
    }
  })
);

app.listen(Number(process.env.PORT || 3000))

这样,前端在访问localhost:3000的时候,就可以获得允许跨域的后端响应。

本地同源代理

另一种方法是,将远端的服务器代理到本地的同源路径下,避免浏览器的跨域检测。以使用打包工具 Parcel-bundler 为例,可以使用bundler.middleware()来返回打包后的静态文件,同时把后端地址设置为/api,并代理所有发送到/api/的请求。

const proxy = require('http-proxy-middleware')
const Bundler = require('parcel-bundler')
const express = require('express')

let bundler = new Bundler('./index.html', {
  publicUrl: '/'
})
let app = express()

app.use(
  '/api',
  proxy({
    target: 'http://test.com',
    changeOrigin: true,
    pathRewrite: function (path, req) {
      return path.replace('/api', '/')
    },
    onProxyRes: function (proxyRes, req, res) {
      proxyRes.headers['Access-Control-Allow-Origin'] = '*';
    }
  })
);

app.use(bundler.middleware())

app.listen(Number(process.env.PORT || 1234))

如果使用前一种方法,前端的请求仍然跨域,只是加上了跨域允许的头部,此时浏览器默认会进行“跨域预检”的操作,具体来说就是请求资源之前发送 OPTIONS 请求来测试跨域是否正常。如果恰巧后端不允许 OPTIONS 方法,则跨域预检无法通过。

使用这种方法,避免了跨域预检操作。要注意的是,如果不进行 pathRewrite 操作,则代理后的 URL 仍会带上 /api/路径,可能与预期并不相符。

  • 开启 Rewirte:
http://localhost:1234/api/login => http://test.com/login
  • 不使用 Rewrite:
http://localhost:1234/api/login => http://test.com/api/login

部署时的跨域解决

部署时,一般则采用 Apache 或者 Nginx 进行代理,例如在 Apache2 中使用 ProxyPass:

ProxyPass /api http://test.com
ProxyPassReverse api http://test.com

当然,直接给后端设置允许跨域也是可以的。