一、跨域问题的本质
1.1 同源策略的三要素
浏览器的同源策略(Same-Origin Policy)要求请求的 协议、域名、端口 完全一致,否则视为跨域:
- 协议不同 :
http
与https
- 域名不同 :
a.com
与b.com
- 端口不同 :
http://a.com:80
与http://a.com:8080
1.2 跨域请求的分类
- 简单请求 (Simple Request):
- HTTP方法:
GET
、POST
、HEAD
- 请求头:仅允许
Accept
、Accept-Language
、Content-Type
(且Content-Type
仅限text/plain
、multipart/form-data
、application/x-www-form-urlencoded
)
- HTTP方法:
- 预检请求 (Preflight Request):
- 当请求包含自定义头(如
Authorization
)或使用非简单方法(如PUT
、DELETE
)时,浏览器会先发送OPTIONS
请求,询问服务器是否允许该请求。
- 当请求包含自定义头(如
二、解决方案一:CORS 标准实现
2.1 CORS 工作原理
CORS 通过 预检协商机制 解决跨域问题:
- 预检请求(OPTIONS) :
- 浏览器发送
OPTIONS
请求,携带请求方法(Access-Control-Request-Method
)和头字段(Access-Control-Request-Headers
)。 - 服务端响应中必须包含允许的
origin
、methods
、headers
,否则浏览器阻止后续请求。
- 浏览器发送
- 实际请求 :
- 若预检通过,浏览器发送真实请求,并携带
Origin
头。 - 服务端在响应头中明确允许的
Access-Control-Allow-Origin
,浏览器才会将数据返回给前端。
- 若预检通过,浏览器发送真实请求,并携带
2.2.1 Spring Boot 实现
java
// 全局CORS配置(application.properties)
spring.mvc.cors.enabled=true
spring.mvc.cors.allow-origins=http://client.example.com
spring.mvc.cors.allow-methods=GET,POST,PUT,DELETE,OPTIONS
spring.mvc.cors.allow-headers=Authorization,Content-Type,X-Requested-With
spring.mvc.cors.exposed-headers=X-Total-Count
spring.mvc.cors.allow-credentials=true
spring.mvc.cors.max-age=3600
// 或通过Java配置
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://client.example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("Authorization", "Content-Type")
.exposedHeaders("X-Total-Count")
.allowCredentials(true)
.maxAge(3600);
}
}
2.2.2 预检请求处理(关键点)
Spring Boot 默认会自动处理 OPTIONS
请求,但需确保:
allowedMethods
包含OPTIONS
allowedHeaders
包含所有自定义头字段
2.3 安全加固
-
动态验证来源 :
java@Bean public CorsWebFilter corsFilter() { return (serverWebExchange, chain) -> { String origin = serverWebExchange.getRequest().getHeaders().getOrigin(); if (allowedOrigins.contains(origin)) { // 设置CORS头 serverWebExchange.getResponse().getHeaders() .add("Access-Control-Allow-Origin", origin); return chain.filter(serverWebExchange); } return Mono.error(new AccessDeniedException("Invalid origin")); }; }
三、解决方案二:JSONP
3.1 JSONP 工作原理
JSONP 利用 <script>
标签的跨域特性,通过动态注入脚本实现数据回传:
- 前端动态注入 :
- 创建
<script>
标签,src
指向服务端接口,附加callback
参数。
- 创建
- 服务端封装数据 :
- 将数据包装在
callback
函数中,返回类似handleResponse({data: "value"})
的字符串。
- 将数据包装在
- 前端执行回调 :
- 浏览器解析脚本,执行
handleResponse
函数,获取数据。
- 浏览器解析脚本,执行
3.2 后端代码
java
@RestController
public class JsonpController {
@GetMapping("/jsonp")
public String handleJsonp(
@RequestParam String callback // 接收前端传递的回调函数名
) {
// 生成模拟数据
Map<String, Object> data = new HashMap<>();
data.put("name", "Alice");
data.put("age", 25);
// 防XSS攻击:校验callback参数格式
if (!callback.matches("^[a-zA-Z0-9_]+$")) {
throw new IllegalArgumentException("Invalid callback parameter");
}
// 将数据封装到回调函数中
return callback + "(" + new Gson().toJson(data) + ")";
}
}
3.3 优缺点对比
优点 | 缺点 |
---|---|
无需服务端配置CORS | 仅支持GET请求 |
兼容性好(支持旧浏览器) | 存在XSS风险(需严格校验callback) |
实现简单 | 不支持复杂认证(如JWT) |
四、解决方案三:Nginx反向代理
4.1 反向代理原理
Nginx 作为反向代理,将客户端请求转发到后端服务,使浏览器认为请求与当前页面同源:
- 请求转发 :
- 客户端请求
http://frontend.com/api/data
- Nginx 将请求转发到
http://backend.com:3000/data
- 客户端请求
- 响应头伪造 :
- Nginx 可修改响应头,如
Access-Control-Allow-Origin
,使浏览器认为请求是同源的。
- Nginx 可修改响应头,如
4.2 配置示例(支持WebSocket与HTTPS)
nginx
server {
listen 443 ssl;
server_name frontend.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location /api/ {
# 反向代理到后端
proxy_pass http://backend:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# CORS配置
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Authorization";
add_header Access-Control-Allow-Credentials "true";
# WebSocket支持
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# 处理OPTIONS预检
location ~ ^/api/ {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '$http_origin';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Max-Age' 86400;
return 204;
}
}
}
4.3 安全加固
-
限制来源 :
nginxmap $http_origin $allowed_origin { default ''; ~^(http://client\.example\.com|https://another\.domain\.com)$ $http_origin; } server { add_header Access-Control-Allow-Origin $allowed_origin always; if ($allowed_origin = '') { return 403; } }
五、解决方案四:API网关(微服务场景)
5.1 API网关核心原理
API网关作为微服务的统一入口,集中处理跨域、认证、限流等逻辑:
- 集中配置CORS :
- 在网关层统一设置
Access-Control-Allow-Origin
,避免每个微服务重复配置。
- 在网关层统一设置
- 路由与安全策略 :
- 根据请求路径路由到对应服务,同时执行身份验证(如JWT校验)。
5.2 Spring Cloud Gateway 实现
java
// 配置全局CORS
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("http://client.example.com"));
config.setAllowedMethods(List.of("GET", "POST"));
config.setAllowedHeaders(List.of("Authorization"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
// 动态路由配置
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("user-service", r -> r.path("/api/user/**")
.filters(f -> f.addRequestHeader("X-Forwarded-Proto", "https"))
.uri("lb://user-service"))
.build();
}
5.3 优势与适用场景
优点 | 适用场景 |
---|---|
集中式管理跨域与安全策略 | 微服务架构、需要统一鉴权的系统 |
支持复杂路由与负载均衡 | 高并发、多服务交互的场景 |
六、解决方案五:代理服务器(Node.js示例)
6.1 代理服务器原理
代理服务器(如Nginx、Node.js)接收客户端请求,转发到目标服务,并修改响应头以绕过跨域限制。
6.2 Node.js实现(http-proxy-middleware)
javascript
// proxy.config.js
module.exports = {
'/api': {
target: 'http://backend.example.com:3000',
changeOrigin: true,
onProxyReq: (proxyReq, req, res) => {
// 动态修改请求头
proxyReq.setHeader('X-Forwarded-For', req.ip);
proxyReq.setHeader('X-Real-IP', req.ip);
},
onProxyRes: (proxyRes, req, res) => {
// 添加CORS头
proxyRes.headers['Access-Control-Allow-Origin'] = req.headers.origin;
}
}
};
6.3 安全建议
-
限制来源 :
javascriptconst allowedOrigins = ['http://client.example.com']; if (!allowedOrigins.includes(req.headers.origin)) { return res.status(403).send('Forbidden'); }
七、解决方案六:服务器端渲染(SSR)
7.1 SSR原理
在服务器端生成完整HTML页面,直接返回给浏览器,避免浏览器发起跨域AJAX请求。
7.2 Next.js 实现
javascript
// pages/index.js
export async function getServerSideProps() {
const res = await fetch('http://api.example.com/data', {
headers: {
Authorization: 'Bearer YOUR_TOKEN'
}
});
const data = await res.json();
return { props: { data } };
}
export default function Home({ data }) {
return <div>{JSON.stringify(data)}</div>;
}
7.3 优势与局限
优点 | 局限 |
---|---|
首屏加载快、SEO友好 | 仅适用于静态或半动态页面 |
无跨域问题 | 开发复杂度较高 |
八、方案选择决策树
场景 | 推荐方案 | 原因 | 技术栈 |
---|---|---|---|
单页应用(SPA)开发 | Nginx反向代理 / 代理服务器 | 开发与生产环境统一配置,避免前后端分离的复杂性 | Node.js, Nginx |
微服务架构 | API网关统一处理 | 集中式管理,支持动态路由与权限控制 | Spring Cloud Gateway, Kong |
旧项目兼容第三方API | JSONP | 无需后端改造,快速集成 | Vanilla JS |
需要严格安全控制 | CORS标准实现 + 白名单 | 细粒度配置,支持所有HTTP方法 | Spring Boot, Express.js |
WebSocket跨域 | Nginx反向代理 + WebSocket支持 | 需要处理Upgrade头和Connection头 | Nginx |
服务端渲染(SSR) | 服务器端直接请求 | 避免浏览器发起跨域请求 | Next.js, Nuxt.js |
九、常见问题与最佳实践
9.1 预检请求(OPTIONS)的深度处理
- 问题:当请求包含自定义头或使用非简单方法(如PUT/DELETE)时,浏览器会先发送OPTIONS请求。
- 解决方案 :
- 在后端显式返回
Access-Control-Allow-Methods
和Access-Control-Allow-Headers
- 对OPTIONS请求返回204 No Content状态码
- 在后端显式返回
9.1.1 Spring Boot示例
java
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("Authorization", "Content-Type", "X-Requested-With");
}
}
9.2 安全性建议
-
避免使用
*
与allowCredentials
同时开启 :javascript// 错误配置 app.use(cors({ origin: '*', credentials: true }));
-
限制
allowedOrigins
为可信域名列表 :javascriptallowedOrigins: ["http://client.example.com", "https://another-domain.com"]
-
对敏感接口启用CSRF防护 :
javascriptapp.use(csrf()); app.use((req, res, next) => { res.cookie('XSRF-TOKEN', req.csrfToken()); next(); });
十、扩展知识点
10.1 WebSocket跨域解决方案
通过Nginx配置支持WebSocket:
nginx
location /ws/ {
proxy_pass http://backend-ws.example.com;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
add_header Access-Control-Allow-Origin $http_origin;
}
10.2 跨域Cookie处理
-
前端设置 :
javascriptfetch('http://api.example.com', { credentials: 'include' // 允许携带Cookie });
-
后端配置 :
nginxadd_header Set-Cookie "SameSite=None; Secure"; // HTTPS下强制跨域Cookie
十一、总结
跨域问题的解决需要结合项目架构、安全需求与开发效率综合考量。CORS作为标准方案应优先采用,而Nginx、API网关等则适用于复杂场景。