总体来说两个解法:
- CorsConfig
- Nginx 代理
核心概念
什么是跨域问题?
跨域问题是浏览器的安全机制(同源策略),当网页从一个域名向另一个域名发送请求时,浏览器会阻止这种请求。
同源策略判断标准
浏览器检查三个要素:协议 + 域名 + 端口
// 当前页面:https://stplan.example.com:443
// ✅ 同源请求(允许)
'https://stplan.example.com:443/api/data'
'https://stplan.example.com/other-page'
'https://stplan.example.com:443/api/user/123'
// ❌ 跨域请求(被阻止)
'http://stplan.example.com/api' // 协议不同(http vs https)
'https://qeelin.example.com/api/data' // 域名不同(stplan vs qeelin)
'https://stplan.example.com:8080/api' // 端口不同(443 vs 8080)
为什么要限制跨域?
1. 保护用户隐私
// 恶意网站 evil.example.com 的页面
// 如果没有跨域限制,可以:
fetch('https://bank.example.com/account/balance') // 窃取银行信息
fetch('https://social.example.com/api/messages') // 窃取私人消息
2. 防止 CSRF 攻击
// 利用用户登录状态执行恶意操作
fetch('https://bank.example.com/transfer', {
method: 'POST',
credentials: 'include',
body: JSON.stringify({
to: 'hacker-account',
amount: 10000
})
});
3. 防止信息泄露
// 探测内网信息
fetch('http://192.168.1.100:8080/admin/users')
.then(() => console.log('发现内网服务!'))
跨域问题的表现
浏览器报错信息
Access to fetch at 'https://qeelin.example.com/api/plan/109909'
from origin 'https://stplan.example.com'
has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
前端代码失败
fetch('https://qeelin.example.com/api/plan/109909')
.then(response => response.json())
.catch(error => {
console.error('跨域请求被阻止!', error);
// 功能无法正常使用
});
重要概念澄清
✅ 跨域只存在于浏览器环境
// ❌ 有跨域问题:浏览器中的网页请求
fetch('https://qeelin.example.com/api/plan/109909')
// ✅ 没有跨域问题:服务器端调用
// Java后端调用其他服务
restTemplate.getForObject("https://qeelin.example.com/api/plan/109909", String.class);
✅ 跨域是前端问题,不是后端问题
- 后端服务之间的调用不受跨域限制
- 只有浏览器中的 JavaScript 请求才会被跨域策略阻止
- 后端可以自由调用任何其他服务
实际场景分析
你的项目情况
前端地址:https://stplan.example.com/#/plans
后端地址:https://qeelin.example.com/api/plan/109909
浏览器检查:
当前页面:stplan.example.com
请求目标:qeelin.example.com
❌ 不同源 → 触发跨域限制
完整的请求流程(有跨域问题)
┌─────────────────────────────────────────────────────────┐
│ 用户浏览器 │
│ 页面:https://stplan.example.com/#/plans │
│ JavaScript 代码: │
│ fetch('https://qeelin.example.com/api/plan/109909') │
│ │
│ 浏览器同源检查: │
│ stplan.example.com ≠ qeelin.example.com │
│ ❌ 不同源 → 这是跨域请求! │
└─────────────────────────────────────────────────────────┘
↓ OPTIONS 预检请求
↓
┌─────────────────────────────────────────────────────────┐
│ 真实后端 (qeelin) │
│ 接收 OPTIONS 预检请求 │
│ 返回 CORS 响应头: │
│ Access-Control-Allow-Origin: ??? │
│ (如果没有或不匹配,浏览器阻止请求) │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 用户浏览器 │
│ ❌ CORS 检查失败 → 浏览器阻止 JavaScript 访问响应 │
│ 错误:No 'Access-Control-Allow-Origin' header │
└─────────────────────────────────────────────────────────┘
跨域解决方案
方案 1:CORS 配置(推荐 ⭐⭐⭐⭐⭐)
原理
在后端响应头中添加 Access-Control-Allow-Origin,告诉浏览器允许这个跨域请求。
实现方式
全局 CORS 配置(推荐)
package com.meituan.stplan.platform.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**") // 匹配 /api 下的所有路径
.allowedOrigins("https://stplan.example.com") // 允许的前端域名
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的 HTTP 方法
.allowedHeaders("*") // 允许所有请求头
.allowCredentials(true) // 允许携带 Cookie
.maxAge(3600); // 预检请求缓存时间(秒)
}
}
单个接口配置
@RestController
@RequestMapping("/api")
public class PlanController {
@CrossOrigin(origins = "https://stplan.example.com")
@GetMapping("/plan/{id}")
public ResponseEntity<?> getPlan(@PathVariable Long id) {
// 业务逻辑
return ResponseEntity.ok(data);
}
}
完整的请求流程(CORS 解决后)
┌─────────────────────────────────────────────────────────┐
│ 用户浏览器 │
│ 页面:https://stplan.example.com/#/plans │
│ 请求:https://qeelin.example.com/api/plan/109909 │
│ │
│ 浏览器同源检查: │
│ stplan.example.com ≠ qeelin.example.com │
│ ❌ 不同源 → 发送 OPTIONS 预检请求 │
└─────────────────────────────────────────────────────────┘
↓ OPTIONS 预检请求
↓
┌─────────────────────────────────────────────────────────┐
│ 真实后端 (qeelin) │
│ 接收 OPTIONS 预检请求 │
│ 返回 CORS 响应头: │
│ HTTP/1.1 200 OK │
│ Access-Control-Allow-Origin: https://stplan.example.com
│ Access-Control-Allow-Methods: GET, POST, PUT, DELETE │
│ Access-Control-Allow-Headers: * │
│ Access-Control-Max-Age: 3600 │
└─────────────────────────────────────────────────────────┘
↓ CORS 检查通过
↓
┌─────────────────────────────────────────────────────────┐
│ 用户浏览器 │
│ ✅ CORS 检查通过 → 发送真实请求 │
│ GET /api/plan/109909 HTTP/1.1 │
└─────────────────────────────────────────────────────────┘
↓ GET 请求
↓
┌─────────────────────────────────────────────────────────┐
│ 真实后端 (qeelin) │
│ 处理请求,返回数据: │
│ { │
│ "id": 109909, │
│ "name": "演练计划", │
│ "status": "进行中" │
│ } │
└─────────────────────────────────────────────────────────┘
↓ 响应数据
↓
┌─────────────────────────────────────────────────────────┐
│ 用户浏览器 │
│ ✅ JavaScript 可以访问响应数据 │
│ 页面更新,显示数据 │
└─────────────────────────────────────────────────────────┘
优点
- ✅ 前端代码不需要改
- ✅ 改动最少(只改后端)
- ✅ 实施最快(几分钟搞定)
- ✅ 不需要运维改 Nginx
- ✅ 维护简单
缺点
- ❌ 后端需要暴露 CORS 配置
- ❌ 如果有多个前端域名,需要都加到白名单
方案 2:Nginx 反向代理(推荐 ⭐⭐⭐)
原理
通过 Nginx 在同一域名下代理请求,浏览器认为前后端在同一域名,不会触发跨域限制。
实现方式
Nginx 配置
server {
listen 443 ssl;
server_name stplan.example.com;
# SSL 证书配置
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# 前端静态资源
location / {
root /var/www/stplan;
try_files $uri $uri/ /index.html;
}
# API 请求代理到 qeelin
location /api/ {
proxy_pass https://qeelin.example.com/;
# 保留原始请求头
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
前端代码改动
// ❌ 原来(会跨域)
fetch('https://qeelin.example.com/api/plan/109909')
// ✅ 改成(通过 Nginx 代理)
fetch('https://stplan.example.com/api/plan/109909')
完整的请求流程(Nginx 反向代理)
┌─────────────────────────────────────────────────────────┐
│ 用户浏览器 │
│ 页面:https://stplan.example.com/#/plans │
│ 请求:https://stplan.example.com/api/plan/109909 │
│ │
│ 浏览器同源检查: │
│ stplan.example.com === stplan.example.com │
│ ✅ 同源 → 不是跨域 → 直接发送请求 │
└─────────────────────────────────────────────────────────┘
↓ 同源请求(没有跨域检查)
↓
┌─────────────────────────────────────────────────────────┐
│ Nginx 服务器 │
│ (浏览器认为这就是 stplan) │
│ │
│ 接收请求:/api/plan/109909 │
│ 匹配 location /api/ 规则 │
│ 转发到:https://qeelin.example.com/api/plan/109909 │
│ (浏览器看不到这一步) │
└─────────────────────────────────────────────────────────┘
↓ 后台转发(浏览器看不到)
↓
┌─────────────────────────────────────────────────────────┐
│ 真实后端 (qeelin) │
│ 处理请求,返回数据: │
│ { │
│ "id": 109909, │
│ "name": "演练计划", │
│ "status": "进行中" │
│ } │
└─────────────────────────────────────────────────────────┘
↓ 返回给 Nginx
↓
┌─────────────────────────────────────────────────────────┐
│ Nginx 服务器 │
│ 返回响应给浏览器 │
└─────────────────────────────────────────────────────────┘
↓ 响应来自 stplan
↓
┌─────────────────────────────────────────────────────────┐
│ 用户浏览器 │
│ 接收响应(认为来自 stplan) │
│ ✅ 没有 CORS 错误 │
│ JavaScript 可以访问响应数据 │
└─────────────────────────────────────────────────────────┘
优点
- ✅ 后端代码不需要改
- ✅ 更安全(后端不暴露 CORS)
- ✅ 性能更好(Nginx 层面处理)
- ✅ 可以统一管理多个后端服务
缺点
- ❌ 需要改前端代码(所有请求 qeelin 的地方改成请求 stplan)
- ❌ 需要改 Nginx 配置
- ❌ 改动范围大
方案 3:后端代理(不推荐)
原理
在前端同域的后端添加代理接口,由后端调用其他服务(后端调用没有跨域限制)。
实现方式
@RestController
@RequestMapping("/api")
public class ProxyController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/proxy/plan/{id}")
public ResponseEntity<?> proxyGetPlan(@PathVariable Long id) {
// 后端调用其他服务,没有跨域问题
String result = restTemplate.getForObject(
"https://qeelin.example.com/api/plan/" + id,
String.class
);
return ResponseEntity.ok(result);
}
}
前端代码
// 请求同域的后端代理接口
fetch('https://stplan.example.com/api/proxy/plan/109909')
.then(res => res.json())
.then(data => console.log(data))
缺点
- ❌ 增加后端负担
- ❌ 需要为每个接口写代理
- ❌ 性能下降(多一层转发)
方案对比
| 方案 | 改前端 | 改后端 | 改 Nginx | 难度 | 推荐度 | 实施时间 |
|---|---|---|---|---|---|---|
| CORS 配置 | ❌ 不需要 | ✅ 需要 | ❌ 不需要 | ⭐ 简单 | ⭐⭐⭐⭐⭐ | 5 分钟 |
| Nginx 反向代理 | ✅ 需要 | ❌ 不需要 | ✅ 需要 | ⭐⭐ 中等 | ⭐⭐⭐ | 30 分钟 |
| 后端代理 | ❌ 不需要 | ✅ 需要 | ❌ 不需要 | ⭐⭐⭐ 复杂 | ⭐ 不推荐 | 1 小时+ |
总结
| 概念 | 说明 |
|---|---|
| 🔒 跨域是什么 | 浏览器的安全机制,保护用户免受恶意攻击 |
| 🌐 跨域的范围 | 只影响浏览器中的请求,服务器间调用不受限制 |
| 🛠️ 主流解决方案 | CORS 配置 + Nginx 反向代理 |
| ✅ 最佳实践 | 使用 CORS 配置,简单快速 |
| 🚫 避免 | 完全关闭跨域限制,会带来安全风险 |