Spring Boot跨域问题终结者!3种配置方式让前后端联调不再头疼
Spring Boot跨域问题终结者!3种配置方式让前后端联调不再头疼
今天我们来聊聊一个让无数Java开发者头疼的问题——跨域请求(CORS)。相信很多小伙伴在前后端分离开发时都遇到过这个问题:
你是否遇到过这样的困扰:
- 前端同学抱怨接口调用报错:"Access-Control-Allow-Origin"问题
- 后端接口明明没问题,但浏览器就是不认
- 网上查了一堆解决方案,试了还是不行
- 项目上线后跨域问题又出现了
别急,今天这篇文章就带你彻底搞懂Spring Boot中的CORS配置,让你从此告别跨域烦恼!
CORS到底是什么鬼?
在深入解决方案之前,我们先来理解一下CORS到底是什么,为什么会出现这个问题。
什么是CORS?
CORS(Cross-Origin Resource Sharing,跨域资源共享)是一种浏览器安全机制,用于限制一个域下的文档或脚本如何与另一个域下的资源进行交互。
什么是"域"?
在Web开发中,"域"由以下三个部分组成:
- 协议:http:// 或 https://
- 域名:www.example.com
- 端口:8080(如果有的话)
只有这三个部分完全相同,才被认为是"同域"。比如:
http://localhost:8080和http://localhost:8081是跨域http://www.example.com和https://www.example.com是跨域http://www.example.com和http://api.example.com是跨域
为什么会有CORS限制?
这是浏览器的一种安全保护机制,叫做同源策略。它的目的是防止恶意网站窃取用户数据。
举个例子:
如果你在恶意网站上,它不能直接访问你的银行网站获取账户信息,因为它们是不同域的。
常见的CORS错误信息
// 前端控制台常见的错误信息
"Access to fetch at 'http://localhost:8080/api/users' from origin 'http://localhost:3000'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource."
// 或者
"Access to XMLHttpRequest at 'http://localhost:8080/api/login' from origin
'http://localhost:3000' has been blocked by CORS policy: Response to preflight
request doesn't pass access control check: It does not have HTTP ok status."
CORS请求的两种类型
- 简单请求:满足特定条件的请求,浏览器直接发送
- 预检请求:复杂请求会先发送OPTIONS请求询问服务器是否允许
简单请求的条件:
- GET、POST、HEAD方法之一
- 只包含安全的请求头(如Accept、Accept-Language、Content-Language等)
- Content-Type只能是:application/x-www-form-urlencoded、multipart/form-data、text/plain
预检请求的条件:
- PUT、DELETE等方法
- 包含自定义请求头
- Content-Type为application/json等
理解了这些基础知识,我们就可以开始配置Spring Boot来解决CORS问题了!
]
Spring Boot CORS配置方法详解
在Spring Boot中,有多种方式可以解决CORS问题。我们来逐一介绍这些方法,并分析它们的适用场景。
方法一:全局配置CORS(推荐)
这是最常用也是最推荐的方式,通过配置类来全局设置CORS规则。
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("/**") // 所有接口都支持跨域
.allowedOriginPatterns("*") // 允许所有域名访问
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的请求方法
.allowedHeaders("*") // 允许的请求头
.allowCredentials(true) // 是否允许携带cookie
.maxAge(3600); // 预检请求的有效期(秒)
}
}
配置说明:
addMapping("/**"):匹配所有接口路径allowedOriginPatterns("*"):允许所有域名访问(Spring Boot 2.4+推荐使用这个)allowedMethods():允许的HTTP方法allowedHeaders("*"):允许的请求头allowCredentials(true):允许携带认证信息(如Cookie)maxAge(3600):预检请求缓存时间
方法二:使用@CrossOrigin注解
在具体的Controller或方法上使用注解,适用于局部配置。
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@RestController
@RequestMapping("/api")
@CrossOrigin(origins = "http://localhost:3000") // 在类级别配置
public class UserController {
@GetMapping("/users")
@CrossOrigin(
origins = {"http://localhost:3000", "http://localhost:4200"},
methods = {RequestMethod.GET, RequestMethod.POST},
allowedHeaders = "*",
allowCredentials = "true"
) // 在方法级别配置,会覆盖类级别的配置
public List<User> getUsers() {
// 返回用户列表
return userService.getAllUsers();
}
@PostMapping("/users")
public User createUser(@RequestBody User user) {
// 创建用户
return userService.createUser(user);
}
@DeleteMapping("/users/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
// 删除用户
userService.deleteUser(id);
return ResponseEntity.ok().build();
}
}
注解参数说明:
origins:允许的源域名methods:允许的HTTP方法allowedHeaders:允许的请求头allowCredentials:是否允许携带认证信息maxAge:预检请求缓存时间
方法三:使用Filter过滤器
通过自定义过滤器来处理CORS请求,这种方式更加灵活。
import org.springframework.stereotype.Component;
import org.springframework.core.annotation.Order;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
@Order(-1) // 确保过滤器优先级最高
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
// 添加CORS响应头
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers",
"Content-Type, Authorization, X-Requested-With");
response.setHeader("Access-Control-Allow-Credentials", "true");
// 处理预检请求
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
}
方法四:通过application.yml配置
在配置文件中也可以进行CORS配置:
# application.yml
spring:
web:
cors:
allowed-origins:
- "http://localhost:3000"
- "http://localhost:4200"
allowed-methods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
allowed-headers: "*"
allow-credentials: true
max-age: 3600
方法五:使用WebFlux配置(适用于响应式编程)
如果你使用的是Spring WebFlux,配置方式略有不同:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
@Configuration
public class CorsWebFluxConfig {
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
config.addAllowedMethod("*");
config.addAllowedHeader("*");
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
各种方法的对比和选择建议
| 配置方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 全局配置 | 配置简单,统一管理 | 灵活性较差 | 项目统一跨域策略 |
| @CrossOrigin注解 | 灵活,可精确控制 | 配置分散,维护困难 | 特定接口需要不同策略 |
| Filter过滤器 | 最灵活,可自定义逻辑 | 代码复杂度高 | 复杂的跨域处理逻辑 |
| 配置文件 | 非侵入式,易于修改 | 功能相对简单 | 简单的跨域需求 |
| WebFlux配置 | 适用于响应式编程 | 仅限WebFlux项目 | 响应式Spring Boot项目 |
推荐使用顺序:
- 首选:全局配置类(方法一)
- 特殊情况:@CrossOrigin注解(方法二)
- 复杂需求:Filter过滤器(方法三)
实际应用案例和最佳实践
让我们通过几个实际的业务场景来看看如何在项目中正确使用CORS配置。
案例一:电商项目前后端分离架构
// 电商项目CORS配置
@Configuration
public class ECommerceCorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**") // 只对API接口开启跨域
.allowedOriginPatterns(
"http://localhost:3000", // 开发环境前端地址
"http://localhost:4200", // Angular开发环境
"https://shop.example.com", // 生产环境前端地址
"https://admin.example.com" // 管理后台地址
)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true) // 允许携带Cookie认证
.maxAge(3600);
// 文件上传接口可能需要更宽松的配置
registry.addMapping("/upload/**")
.allowedOriginPatterns("*")
.allowedMethods("POST", "OPTIONS")
.allowedHeaders("*")
.maxAge(1800);
}
}
// 用户控制器
@RestController
@RequestMapping("/api/users")
@CrossOrigin(origins = "https://admin.example.com") // 管理后台专用
public class UserController {
@Autowired
private UserService userService;
// 获取用户列表(管理后台专用)
@GetMapping
public ResponseEntity<List<User>> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
List<User> users = userService.getUsers(page, size);
return ResponseEntity.ok(users);
}
// 用户注册(所有前端都可以访问)
@PostMapping("/register")
public ResponseEntity<User> register(@RequestBody UserRegistrationDto dto) {
User user = userService.register(dto);
return ResponseEntity.ok(user);
}
// 用户登录
@PostMapping("/login")
public ResponseEntity<LoginResponse> login(@RequestBody LoginDto dto) {
LoginResponse response = userService.login(dto);
return ResponseEntity.ok(response);
}
}
// 订单控制器
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private OrderService orderService;
// 创建订单(需要用户认证)
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody CreateOrderDto dto,
HttpServletRequest request) {
// 从请求中获取用户信息
String userId = (String) request.getAttribute("userId");
Order order = orderService.createOrder(userId, dto);
return ResponseEntity.ok(order);
}
// 获取用户订单(需要用户认证)
@GetMapping("/my")
public ResponseEntity<List<Order>> getMyOrders(HttpServletRequest request) {
String userId = (String) request.getAttribute("userId");
List<Order> orders = orderService.getUserOrders(userId);
return ResponseEntity.ok(orders);
}
}
案例二:微服务架构中的CORS处理
// 网关层统一处理CORS
@Configuration
public class GatewayCorsConfig {
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration config = new CorsConfiguration();
// 根据不同环境设置允许的源
if (isDevEnvironment()) {
config.addAllowedOriginPattern("*");
} else {
config.addAllowedOriginPattern("https://*.example.com");
config.addAllowedOriginPattern("https://example.com");
}
config.addAllowedMethod("*");
config.addAllowedHeader("*");
config.setAllowCredentials(true);
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
private boolean isDevEnvironment() {
String env = System.getenv("SPRING_PROFILES_ACTIVE");
return "dev".equals(env) || "local".equals(env);
}
}
// 各个微服务中禁用CORS(由网关统一处理)
@Configuration
public class MicroserviceCorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 微服务之间调用不需要CORS,由API网关统一处理
// registry.addMapping("/**").allowedOrigins(); // 留空或注释掉
}
}
案例三:多域名支持的复杂配置
// 复杂的多域名CORS配置
@Configuration
public class ComplexCorsConfig implements WebMvcConfigurer {
@Value("${app.cors.allowed-origins:}")
private String[] allowedOrigins;
@Value("${app.cors.allowed-origins-patterns:}")
private String[] allowedOriginPatterns;
@Override
public void addCorsMappings(CorsRegistry registry) {
CorsRegistration corsRegistration = registry.addMapping("/api/**")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.exposedHeaders("X-Total-Count", "X-Pagination")
.allowCredentials(true)
.maxAge(3600);
// 根据配置决定使用allowedOrigins还是allowedOriginPatterns
if (allowedOrigins != null && allowedOrigins.length > 0) {
corsRegistration.allowedOrigins(allowedOrigins);
} else if (allowedOriginPatterns != null && allowedOriginPatterns.length > 0) {
corsRegistration.allowedOriginPatterns(allowedOriginPatterns);
} else {
// 默认配置
corsRegistration.allowedOriginPatterns(
"http://localhost:*",
"https://*.example.com",
"https://example.com"
);
}
}
}
// application.yml配置
/*
app:
cors:
allowed-origins-patterns:
- "http://localhost:*"
- "https://*.example.com"
- "https://example.com"
allowed-origins:
# - "http://localhost:3000"
# - "https://shop.example.com"
*/
最佳实践建议
1. 安全性考虑
// 安全的CORS配置示例
@Configuration
public class SecureCorsConfig implements WebMvcConfigurer {
@Value("${app.env:prod}")
private String environment;
@Override
public void addCorsMappings(CorsRegistry registry) {
CorsRegistration registration = registry.addMapping("/api/**")
.allowedMethods("GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS")
.allowedHeaders(
"Authorization",
"Content-Type",
"X-Requested-With",
"Accept",
"Origin",
"Access-Control-Request-Method",
"Access-Control-Request-Headers"
)
.exposedHeaders("Authorization", "X-Total-Count")
.maxAge(3600);
// 根据环境设置不同的源策略
if ("dev".equals(environment) || "local".equals(environment)) {
// 开发环境允许所有源
registration.allowedOriginPatterns("*");
registration.allowCredentials(false); // 开发环境不允 许凭证
} else {
// 生产环境只允许特定源
registration.allowedOriginPatterns(
"https://shop.example.com",
"https://admin.example.com",
"https://mobile.example.com"
);
registration.allowCredentials(true);
}
}
}
2. 性能优化
// 高性能CORS配置
@Configuration
public class PerformanceCorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 为不同类型的接口设置不同的CORS策略
// API接口 - 标准配置
registry.addMapping("/api/**")
.allowedOriginPatterns("https://*.example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("Authorization", "Content-Type")
.allowCredentials(true)
.maxAge(3600); // 长缓存时间
// 公开接口 - 更宽松的配置
registry.addMapping("/public/**")
.allowedOriginPatterns("*")
.allowedMethods("GET")
.allowedHeaders("Content-Type")
.maxAge(86400); // 24小时缓存
// 文件上传接口 - 特殊配置
registry.addMapping("/upload/**")
.allowedOriginPatterns("https://*.example.com")
.allowedMethods("POST")
.allowedHeaders("Content-Type", "X-File-Name")
.maxAge(1800);
}
}
3. 日志记录和监控
// 带日志记录的CORS过滤器
@Component
@Order(-2)
public class LoggingCorsFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(LoggingCorsFilter.class);
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String origin = request.getHeader("Origin");
String method = request.getMethod();
// 记录CORS请求
if (origin != null) {
logger.info("CORS request from origin: {}, method: {}", origin, method);
}
// 设置CORS头
response.setHeader("Access-Control-Allow-Origin",
getAllowedOrigin(origin));
response.setHeader("Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers",
"Authorization, Content-Type, X-Requested-With");
// 处理预检请求
if ("OPTIONS".equalsIgnoreCase(method)) {
response.setStatus(HttpServletResponse.SC_OK);
logger.debug("Handled preflight request from: {}", origin);
return;
}
chain.doFilter(req, res);
}
private String getAllowedOrigin(String origin) {
// 根据业务逻辑判断是否允许该源
Set<String> allowedOrigins = Set.of(
"https://shop.example.com",
"https://admin.example.com"
);
return allowedOrigins.contains(origin) ? origin : "null";
}
}
常见问题排查和解决方案
兄弟们,在实际开发过程中,即使我们按照标准配置了CORS,还是可能会遇到一些让人头疼的问题。今天我就来给大家梳理一下最常见的几个坑,以及对应的解决方案。
1. 配置不生效问题
这是最常见的情况,你明明配置了CORS,但前端还是报跨域错误。这时候我们得按以下步骤来排查:
首先,检查你的配置是否真的被加载了。可以用这个小技巧:
@Component
public class CorsConfigChecker implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("CORS配置已加载!");
}
}
如果控制台没打印这条信息,说明你的配置类可能没有被Spring扫描到。检查一下:
- 配置类上是否有
@Configuration注解 - 是否在Spring Boot启动类的扫描路径下
- 包名是否正确
2. 预检请求403错误
当遇到OPTIONS请求返回403时,通常是因为我们的拦截器或过滤器把预检请求给拦住了。解决方案是在拦截器中放行OPTIONS请求:
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 放行OPTIONS预检请求
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
return true;
}
// 其他请求继续处理
return super.preHandle(request, response, handler);
}
3. Cookie无法传递问题
如果你需要携带Cookie进行跨域请求,光配置allowCredentials(true)还不够,前端也要配合设置:
// 前端axios配置
axios.defaults.withCredentials = true;
同时后端要注意,当设置了allowCredentials(true)时,allowedOrigins不能使用通配符"*",必须指定具体的域名:
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://www.yourdomain.com") // 注意这里不能用*
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowCredentials(true)
.maxAge(3600);
}
}
4. 多个域名配置问题
有时候我们需要支持多个域名访问,这时候可以这样配置:
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOriginPatterns("*") // 使用Pattern匹配多个域名
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowCredentials(true)
.maxAge(3600);
}
}
或者更精确地控制:
.allowedOrigins("https://www.domain1.com", "https://www.domain2.com")
5. 请求头限制问题
如果前端需要传递自定义请求头,比如X-Requested-With,后端也需要明确允许:
registry.addMapping("/api/**")
.allowedOrigins("https://www.yourdomain.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*") // 允许所有请求头
.exposedHeaders("Authorization") // 暴露特定响应头给前端
.allowCredentials(true);
6. 调试技巧分享
最后分享一个小技巧,如何快速验证CORS配置是否正确。可以在浏览器控制台执行以下命令:
fetch('http://localhost:8080/api/test', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({test: 'data'})
})
.then(response => console.log(response))
.catch(error => console.error('Error:', error));
如果能看到正确的响应而不是跨域错误,说明配置就成功了!
好啦,常见的问题和解决方案就这些。大家在开发中遇到其他问题,欢迎在评论区留言讨论,我们一起解决!
标题:Spring Boot跨域问题终结者!3种配置方式让前后端联调不再头疼
作者:jiangyi
地址:http://jiangyi.space/articles/2025/12/21/1766304289183.html
- CORS到底是什么鬼?
- 什么是CORS?
- 什么是"域"?
- 为什么会有CORS限制?
- 常见的CORS错误信息
- CORS请求的两种类型
- 简单请求的条件:
- 预检请求的条件:
- Spring Boot CORS配置方法详解
- 方法一:全局配置CORS(推荐)
- 方法二:使用@CrossOrigin注解
- 方法三:使用Filter过滤器
- 方法四:通过application.yml配置
- 方法五:使用WebFlux配置(适用于响应式编程)
- 各种方法的对比和选择建议
- 实际应用案例和最佳实践
- 案例一:电商项目前后端分离架构
- 案例二:微服务架构中的CORS处理
- 案例三:多域名支持的复杂配置
- 最佳实践建议
- 1. 安全性考虑
- 2. 性能优化
- 3. 日志记录和监控
- 常见问题排查和解决方案
- 1. 配置不生效问题
- 2. 预检请求403错误
- 3. Cookie无法传递问题
- 4. 多个域名配置问题
- 5. 请求头限制问题
- 6. 调试技巧分享