跨域 CORS 完整解决方案:后端、Nginx、前端代理全覆盖

跨域的本质

浏览器的同源策略阻止了跨源请求:协议、域名、端口任一不同即为跨域。服务端不受此限制,同源策略是浏览器行为,后端直接请求后端不存在跨域问题。

浏览器在发送非简单请求前会先发一个 OPTIONS 预检请求(preflight),服务端必须正确响应:

Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization

后端配置(根本解法)

Node.js / Express

最简单的做法是手动加响应头,或使用 cors 中间件:

// 手动写
app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
  res.header("Access-Control-Allow-Headers", "Content-Type,Authorization");
  if (req.method === "OPTIONS") return res.sendStatus(200);
  next();
});

// 或使用 cors 包
const cors = require("cors");
app.use(cors({
  origin: "https://your-frontend.com",
  methods: ["GET", "POST"],
  allowedHeaders: ["Content-Type", "Authorization"]
}));

生产环境不要用 *,写具体允许的 origin。带 credentials 时更不能用 *

app.use(cors({
  origin: "https://your-frontend.com",
  credentials: true  // 允许 Cookie
}));

Spring Boot

单个接口:

@CrossOrigin(origins = "*", methods = {RequestMethod.GET, RequestMethod.POST})
@RestController
public class MyController {
    @GetMapping("/api/data")
    public String getData() { ... }
}

全局配置(推荐统一管理):

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("https://your-frontend.com")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600);
    }
}

Nginx 反向代理配置

适合把前端和后端统一在同一域名下,或给第三方接口套一层代理:

location /api/ {
    proxy_pass http://backend:8080;

    add_header Access-Control-Allow-Origin  $http_origin always;
    add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
    add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
    add_header Access-Control-Allow-Credentials true always;

    if ($request_method = OPTIONS) {
        return 204;
    }
}

always 参数确保在非 200 响应(如 4xx、5xx)时也带上跨域头。

前端开发环境代理

开发阶段用本地代理把请求转发到后端,不产生跨域:

Vite:

// vite.config.js
export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
});

Vue CLI / webpack-dev-server:

// vue.config.js
module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true
      }
    }
  }
};

Create React App:

// package.json
{
  "proxy": "http://localhost:8080"
}

JSONP(仅 GET,老项目兼容)

利用 <script> 标签不受同源限制:

<script src="https://api.example.com/data?callback=onData"></script>
<script>
function onData(data) {
    console.log(data);  // { "name": "test" }
}
</script>

服务端返回:

onData({"name":"test"})

JSONP 只支持 GET,现代项目不推荐,但对接老旧第三方接口时可能还会遇到。

后端转发(BFF 模式)

前端请求自己的服务端,由服务端再去请求第三方接口:

浏览器 → 自己的后端(同源)→ 第三方 API

这样浏览器始终只看到同源请求,完全绕开 CORS 限制。适合第三方接口不支持配置 CORS 的情况。

方案速查

场景推荐方案
生产环境,自己控制后端后端配置 CORS 响应头
生产环境,用 NginxNginx 加 Access-Control-* 头
开发环境调试本地代理(Vite/Vue CLI/CRA)
第三方接口不支持 CORS后端转发(BFF)
老项目只有 GET 需求JSONP
临时调试(不上线)浏览器 CORS 插件