每次看到浏览器控制台那个鲜红的CORS错误提示,是不是感觉血压都上来了?作为一个经历过无数次跨域折磨的老司机,我完全理解这种痛苦。想象一下这样的场景:你花了两天时间开发的前端页面,调用自己写的后端接口时,浏览器却无情地抛出了"Access-Control-Allow-Origin"错误。这种挫败感,我懂。
其实CORS问题本质上是个"保安问题"。浏览器就像个尽职的保安,严格执行"同源政策"——只允许页面与相同协议、域名和端口的服务通信。这个机制虽然保护了用户安全,却给开发者带来了不少麻烦。特别是在前后端分离架构成为主流的今天,前端运行在localhost:3000,后端在localhost:8080,这种跨域情况简直不要太常见。
我见过太多开发者习惯性地在遇到CORS错误时,才手忙脚乱地去找解决方案。这种"事后补救"的做法不仅效率低下,还可能导致安全隐患。正确的做法应该是在项目搭建之初,就系统性地规划好跨域策略。这样不仅能避免开发过程中的各种阻塞,还能确保生产环境的安全性。
让我们从最基础的开始——创建一个全新的Spring Boot项目。我强烈推荐使用Spring Initializr(https://start.spring.io/),这是官方提供的项目初始化工具,简单易用。
打开网页后,我们需要做几个关键选择:
点击生成按钮后,你会下载到一个压缩包。解压后用你喜欢的IDE(IntelliJ IDEA或Eclipse)打开。项目结构应该类似这样:
code复制todo-api/
├── src/
│ ├── main/
│ │ ├── java/com/example/todoapi
│ │ │ ├── TodoApiApplication.java
│ │ ├── resources
│ │ │ ├── application.properties
├── build.gradle
为了演示CORS配置,我们先创建一个简单的待办事项API。在java/com/example/todoapi目录下新建TodoController.java:
java复制@RestController
@RequestMapping("/api/todos")
public class TodoController {
private List<Todo> todos = new ArrayList<>();
@GetMapping
public List<Todo> getAllTodos() {
return todos;
}
@PostMapping
public Todo createTodo(@RequestBody Todo todo) {
todos.add(todo);
return todo;
}
}
class Todo {
private String id;
private String title;
private boolean completed;
// 省略getter/setter
}
现在启动应用,访问http://localhost:8080/api/todos应该能看到空数组[]。但如果你尝试从前端项目调用这个接口,就会遇到经典的CORS错误。
最推荐的方式是创建一个专门的配置类。在config包下新建CorsConfig.java:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
这个配置做了以下几件事:
/** 匹配所有路径如果你只想为特定控制器或方法启用CORS,可以使用@CrossOrigin注解:
java复制@RestController
@RequestMapping("/api/todos")
@CrossOrigin(origins = "http://localhost:3000")
public class TodoController {
// ...
}
或者在方法级别:
java复制@GetMapping
@CrossOrigin(origins = "http://localhost:3000")
public List<Todo> getAllTodos() {
return todos;
}
这种方式虽然简单,但不推荐在生产环境大规模使用,因为维护成本高且容易遗漏。
对于更复杂的需求,比如需要动态判断origin,可以使用过滤器:
java复制@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("http://localhost:3000");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new FilterRegistrationBean<>(new CorsFilter(source));
}
开发环境和生产环境的CORS配置通常不同。我推荐使用Spring Profile来管理:
java复制@Profile("dev")
@Configuration
public class DevCorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("*");
}
}
@Profile("prod")
@Configuration
public class ProdCorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://your-production-domain.com")
.allowedMethods("GET", "POST");
}
}
然后在application.properties中激活对应的profile:
code复制spring.profiles.active=dev
千万不要直接使用allowedOrigins("*")!这相当于把大门完全敞开,任何网站都可以调用你的API。我见过太多项目因为这样配置导致安全问题。
如果API需要支持多个域名,可以这样配置:
java复制@Value("${allowed.origins}")
private String[] allowedOrigins;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(allowedOrigins)
// 其他配置...
}
然后在application.properties中:
code复制allowed.origins=https://domain1.com,https://domain2.com
对于复杂请求(如带自定义头的POST请求),浏览器会先发送OPTIONS预检请求。确保你的配置正确处理这类请求:
java复制@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 必须包含OPTIONS
// 其他配置...
}
Postman等工具不受同源策略限制,但你可以用它来检查响应头:
在Chrome开发者工具的Network标签中:
如果配置看起来正确但依然报错,检查以下几点:
当项目引入Spring Security后,CORS配置需要额外注意。默认情况下Spring Security会覆盖你的CORS配置,需要在安全配置中明确启用:
java复制@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and() // 启用CORS
// 其他安全配置...
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
记得有一次,我在一个生产项目上花了整整一天排查CORS问题。配置看起来完全正确,但前端就是报错。最后发现是因为Nginx代理修改了响应头。解决方案是在Nginx配置中添加:
nginx复制location /api {
proxy_pass http://backend;
add_header 'Access-Control-Allow-Origin' 'https://frontend-domain.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
# 其他必要头...
}
另一个常见问题是带凭证的请求。如果前端设置了withCredentials: true,那么后端必须:
allowCredentials(true)*作为allowedOriginsjava复制registry.addMapping("/**")
.allowedOrigins("http://localhost:3000") // 必须是具体origin,不能是*
.allowCredentials(true) // 允许凭证
.allowedHeaders("content-type", "authorization"); // 明确列出需要的头