CORS错误全解析:从原理到实战解决方案

常青的笔记 安全 阅读 877
赞 10 收藏
二维码
手机扫码查看
反馈

又遇到 CORS 错误了?别慌,这几个方案我踩过坑

上周改个老项目接口,本地跑得好好的,一上测试环境直接报 CORS 错误。这玩意儿我见得多了,但每次处理还是得翻半天配置。其实解决 CORS 的方式就那么几种,但不同场景下选哪个真得看情况。今天我就把几个常用方案拉出来比一比,说说我实际用下来的感受。

CORS错误全解析:从原理到实战解决方案

谁更灵活?谁更省事?

先说结论:能改后端就改后端,不能改就上代理,实在不行再考虑 JSONP(但基本不推荐)。下面一个个拆开说。

最正统、最干净的做法,当然是让后端在响应头里加上正确的 CORS 配置。比如 Node.js + Express:

const express = require('express');
const app = express();

app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', 'https://your-frontend.com');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  next();
});

或者更偷懒一点,开发阶段直接允许所有来源(上线前记得改回来):

res.header('Access-Control-Allow-Origin', '*');

我比较喜欢这种方式,因为一劳永逸。只要后端配好,前端完全不用动,fetchaxios 直接调就行:

fetch('https://jztheme.com/api/data')
  .then(res => res.json())
  .then(data => console.log(data));

但问题来了:很多时候你根本碰不到后端代码。比如对接第三方 API,或者公司里前后端分离严重,后端团队排期比你还满。这时候你就只能自己想办法。

本地开发神器:Vite / Webpack Dev Server 代理

这种情况我一般首选开发服务器代理。比如 Vite 的配置:

// vite.config.js
export default {
  server: {
    proxy: {
      '/api': {
        target: 'https://jztheme.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^/api/, ''),
      },
    },
  },
};

然后前端代码里这么写:

fetch('/api/data') // 实际请求被代理到 https://jztheme.com/api/data

这个方案我亲测有效,而且只影响开发环境,上线时该配 Nginx 还是配 Nginx,互不干扰。Webpack 的 devServer.proxy 也类似,配置逻辑差不多。

不过这里有个坑我踩过好几次:changeOrigin: true 必须加!不然有些后端会校验 Host 头,导致 403。另外,如果 API 路径不是统一 /api 前缀,rewrite 规则就得写仔细点,不然容易漏掉参数。

总的来说,这个方案适合绝大多数前端主导的项目,尤其是你控制不了后端的时候。简单、安全、不影响生产环境。

JSONP?别想了,除非你活在 2012 年

有些老教程还会提 JSONP,比如这样:

<script>
  function handleData(data) {
    console.log(data);
  }
</script>
<script src="https://jztheme.com/api/data?callback=handleData"></script>

理论上它能绕过 CORS,因为 <script> 标签不受同源策略限制。但问题一大堆:

  • 只支持 GET 请求,POST、PUT 想都别想
  • 错误处理几乎为零,网络失败或接口异常很难捕获
  • 安全性差,相当于执行远程代码,万一对方返回恶意脚本就完了

我去年维护一个遗留系统时被迫用过一次 JSONP,结果调试了整整一天才搞定回调作用域的问题。从那以后我发誓:除非万不得已,绝不碰 JSONP。现在连很多 CDN 都默认禁用 JSONP 了,何必自找麻烦?

生产环境怎么办?Nginx 上场

开发用代理,上线总不能还靠 Vite 吧?这时候就得靠反向代理了。我一般在 Nginx 里加几行:

location /api/ {
    proxy_pass https://jztheme.com/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    add_header Access-Control-Allow-Origin "https://your-frontend.com";
    add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
    add_header Access-Control-Allow-Headers "Content-Type, Authorization";
}

这样前端请求 /api/xxx 就会被转发到目标服务器,同时由 Nginx 添加 CORS 头。好处是你完全掌控响应头,而且不依赖后端配合。

不过要注意:add_header 在 Nginx 里是有继承规则的,如果外面已经有 add_header,里面的可能不会生效。我之前就因为这个折腾了半天,最后把所有 header 都写在同一个 location 块里才搞定。

另外,如果后端本身已经设置了 CORS 头,Nginx 再加可能会冲突。这时候可以用 proxy_hide_header 先去掉后端的头,再自己加新的。

我的选型逻辑

总结一下我的实际选择顺序:

  1. 能改后端?直接让后端加 CORS 头 —— 最干净,一劳永逸
  2. 不能改后端,但只是本地开发?用 Vite/Webpack 代理 —— 简单高效,不影响线上
  3. 上线部署?上 Nginx 反向代理 + 手动加 CORS 头 —— 灵活可控,适合生产
  4. JSONP?除非接口只读、只 GET、且你信任对方,否则别碰

其实还有个极端方案:把前端和后端部署在同一个域名下,比如用子路径 /app/api。这样根本不会有跨域问题。不过现在很多项目用 SPA + 独立 API,这种架构不太现实。

另外提醒一句:CORS 错误有时候不是后端没配,而是前端发的请求带了 credentials(比如 withCredentials: true),但后端没配 Access-Control-Allow-Credentials: true。这种细节特别容易忽略,浏览器报错又不明确,查起来很头疼。所以看到 CORS 错误先看 Network 面板里的实际请求头和响应头,别光看控制台报错。

结尾碎碎念

说实话,CORS 本身不是什么复杂机制,但每次遇到还是得花时间排查。主要是因为它涉及前后端协作,而现实中经常出现“我以为你配了”“我以为你不需要”的沟通黑洞。

以上是我踩坑后的总结,希望对你有帮助。如果你有更好的方案,比如用 Cloudflare Workers 做中间层之类的,欢迎评论区交流。毕竟前端这行,永远有更骚的操作等着我们去发现。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论