跨域的本质
浏览器的同源策略阻止了跨源请求:协议、域名、端口任一不同即为跨域。服务端不受此限制,同源策略是浏览器行为,后端直接请求后端不存在跨域问题。
浏览器在发送非简单请求前会先发一个 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 响应头 |
| 生产环境,用 Nginx | Nginx 加 Access-Control-* 头 |
| 开发环境调试 | 本地代理(Vite/Vue CLI/CRA) |
| 第三方接口不支持 CORS | 后端转发(BFF) |
| 老项目只有 GET 需求 | JSONP |
| 临时调试(不上线) | 浏览器 CORS 插件 |
