一、跨域问题的本质
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 {@Overridepublic 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)
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 安全加固
- 限制来源:
map $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 安全建议
- 限制来源:
javascript">const 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 {@Overridepublic 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
为可信域名列表:javascript">allowedOrigins: ["http://client.example.com", "https://another-domain.com"]
- 对敏感接口启用CSRF防护:
javascript">app.use(csrf()); app.use((req, res, next) => {res.cookie('XSRF-TOKEN', req.csrfToken());next(); });
十、扩展知识点
10.1 WebSocket跨域解决方案
通过Nginx配置支持WebSocket:
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处理
- 前端设置:
javascript">fetch('http://api.example.com', {credentials: 'include' // 允许携带Cookie });
- 后端配置:
add_header Set-Cookie "SameSite=None; Secure"; // HTTPS下强制跨域Cookie
十一、总结
跨域问题的解决需要结合项目架构、安全需求与开发效率综合考量。CORS作为标准方案应优先采用,而Nginx、API网关等则适用于复杂场景。