在前后端分离开发模式中,跨域是一个绕不开的技术难题。很多开发者初次遇到跨域时,都会陷入“接口能通但前端拿不到数据”的困惑,甚至误以为是后端接口出现故障。其实跨域并非接口问题,而是浏览器为了保障安全设置的同源策略限制。本文将从跨域的本质出发,拆解其产生原因、典型表现,再结合实战代码,详解SpringBoot环境下四种常用的跨域解决方案,帮助开发者快速搞定跨域难题。
一、为什么会出现跨域?核心是浏览器的同源策略
跨域的产生,根源在于浏览器的同源策略——这是浏览器最基础的安全机制,目的是防止恶意网站通过JavaScript脚本窃取其他网站的敏感数据,比如用户登录态、个人信息等。所谓“同源”,就是指两个网络地址的协议、域名、端口必须完全一致,只要有一个维度不同,就会被浏览器判定为不同源,从而触发跨域限制。
1. 同源判定的3个核心维度
浏览器判断两个地址是否同源,严格遵循以下3个标准,缺一不可:
- 协议一致:比如都是http或都是https,http与https属于不同源;
- 域名一致:比如www.test.com 和www.demo.com 属于不同源,即使是localhost和127.0.0.1,也会被判定为不同域名;
- 端口一致:比如http://localhost:8080 和http://localhost:9090 ,因端口不同,属于不同源。
2. 常见跨域场景示例
结合日常开发场景,以下几种情况均会触发跨域,大家可以对照自查:
# 场景1:协议不同
前端:http://localhost:8080 → 后端:https://localhost:8080 (跨域)
# 场景2:域名不同
前端:http://www.a.com → 后端:http://www.b.com (跨域)
# 场景3:端口不同
前端:http://localhost:8080 → 后端:http://localhost:9090 (跨域)
# 场景4:域名别名不同
前端:http://localhost:8080 → 后端:http://127.0.0.1:8080 (跨域)
这里需要特别注意:跨域是浏览器专属的限制,服务器与服务器之间的通信(比如Java后端通过HttpClient调用其他服务接口),不会受到同源策略约束,因此不存在跨域问题。
二、跨域的典型表现:请求发得出,数据收不到
很多开发者对跨域的误解是“接口请求失败”,但实际情况是:跨域请求本身能正常到达后端服务器,后端也会正常处理请求并返回数据,但浏览器在接收响应时,会检查响应头中是否包含合规的CORS(跨域资源共享)头,若没有则会拦截响应数据,并在控制台抛出跨域报错,导致前端无法获取数据。
1. 浏览器控制台典型跨域报错
当出现跨域时,浏览器控制台会输出类似以下的报错信息,这是判断跨域问题的核心依据:
Access to fetch at 'http://localhost:9090/api/user' from origin 'http://localhost:8080' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
2. 实战模拟:复现跨域场景
为了更直观地感受跨域现象,我们通过简单的前后端代码,模拟一个跨域场景,帮助大家理解其表现。
后端(SpringBoot,端口9090):编写一个简单的用户接口
package com.example.corsdemo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
public class UserController {
@GetMapping("/user")
public String getUserInfo() {
// 模拟返回用户信息
return "{'username': 'test', 'age': 20}";
}
}
后端配置文件(application.yml),指定端口9090:
server:
port: 9090
前端(HTML+JS,运行在8080端口):发起跨域请求
启动后端和前端后,点击“获取用户信息”按钮,前端会显示请求失败,打开浏览器控制台,就能看到跨域报错,这就是跨域的典型表现——请求发出去了,但数据被浏览器拦截了。
三、SpringBoot解决跨域:4种实战方案(覆盖所有场景)
SpringBoot解决跨域的核心思路是:让后端接口返回合规的CORS跨域响应头,告诉浏览器“该接口允许跨域访问”,从而放行响应数据。下面介绍4种常用方案,分别适配不同的开发场景,大家可根据项目需求灵活选择。
方案一:全局跨域配置(单体项目首选)
全局跨域配置是最常用的方案,通过编写配置类,让整个项目的所有接口都支持跨域,无需在每个接口单独配置,开发效率高、无侵入性,适合单体项目使用。
package com.example.corsdemo.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 CorsGlobalConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 匹配所有接口路径
.allowedOriginPatterns("*") // 允许所有跨域源(生产环境建议指定具体域名)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许所有请求方法,必须放行OPTIONS预检请求
.allowedHeaders("*") // 允许所有请求头(如token、Content-Type)
.allowCredentials(true) // 允许携带Cookie,前后端分离带登录态必备
.maxAge(3600); // 预检请求有效期(秒),期间无需重复发起预检
}
}
配置说明:SpringBoot 2.4及以上版本,推荐使用allowedOriginPatterns(""),替代过时的allowedOrigins(""),可解决通配符与allowCredentials=true同时配置时的冲突问题。
方案二:局部跨域配置(精准控制接口)
当项目中只有部分接口需要跨域,其余接口无需开放跨域权限时,适合使用局部配置。通过@CrossOrigin注解,可实现接口级或控制器级的精准控制,其优先级高于全局配置。
方式1:接口级配置(单个接口生效)
package com.example.corsdemo.controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
public class UserController {
// 仅当前接口允许跨域,其余接口不受影响
@CrossOrigin(origins = "*", allowCredentials = true, maxAge = 3600)
@GetMapping("/user")
public String getUserInfo() {
return "{'username': 'test', 'age': 20}";
}
// 该接口不配置跨域,不允许跨域访问
@GetMapping("/user/count")
public String getUserCount() {
return "100";
}
}
方式2:控制器级配置(整个控制器接口生效)
package com.example.corsdemo.controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
// 该控制器下所有接口统一允许跨域
@RestController
@RequestMapping("/order")
@CrossOrigin(origins = "*", allowCredentials = true, maxAge = 3600)
public class OrderController {
@GetMapping("/list")
public String getOrderList() {
return "订单列表(支持跨域)";
}
@GetMapping("/detail")
public String getOrderDetail() {
return "订单详情(支持跨域)";
}
}
方案三:Nginx反向代理(生产环境优选)
跨域是浏览器的限制,服务器之间通信无跨域问题。通过Nginx将前端项目和后端接口代理到同一个域名和端口,消除同源差异,既能解决跨域,又能隐藏后端真实端口,提升系统安全性,适合生产环境使用。
假设前端运行在8080端口,后端运行在9090端口,Nginx核心配置如下:
server {
listen 80; # Nginx监听80端口
server_name localhost; # 代理域名
# 代理前端项目:http://localhost → 前端服务(8080端口)
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 代理后端接口:http://localhost/api → 后端服务(9090端口)
location /api {
proxy_pass http://localhost:9090;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
rewrite ^/api/(.*)$ /$1 break; # 去掉/api前缀,适配后端真实接口
}
}
配置后,前端请求后端接口时,只需写http://localhost/api/user,无需指定9090端口,Nginx会自动转发请求,浏览器判定同源,不会出现跨域问题。
方案四:SpringCloud网关跨域(微服务专属)
在微服务架构中,所有请求都会经过SpringCloud Gateway网关,此时只需在网关层统一配置跨域,所有微服务无需单独配置,避免出现跨域头重复导致的跨域失败。
SpringCloud Gateway核心配置(application.yml):
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]': # 匹配所有请求路径
allowed-origins: "*" # 允许所有跨域源
allowed-methods: "*" # 允许所有请求方法
allowed-headers: "*" # 允许所有请求头
allow-credentials: true # 允许携带Cookie
max-age: 3600 # 预检请求有效期(秒)
四、核心总结
跨域的本质是浏览器的同源策略限制,解决跨域的核心是让后端返回合规的CORS跨域响应头。不同场景对应不同的解决方案,选择合适的方案能大幅提升开发效率:
- 单体项目:优先使用全局跨域配置,一次配置,全项目生效;
- 部分接口跨域:使用@CrossOrigin局部注解,精准控制,灵活适配;
- 生产环境:优先使用Nginx反向代理,兼顾性能与安全性;
- 微服务架构:使用SpringCloud网关统一配置,避免重复配置。
掌握以上方案,就能轻松应对开发中绝大多数跨域问题,同时理解跨域的核心逻辑,也能在面试中从容应对相关提问。