跨域处理
CORS(Cross-Origin Resource Sharing,跨源资源共享)是浏览器的安全机制,当前端与后端的 协议 / 域名 / 端口 任一不同时,浏览器会拦截响应。
CORS 工作原理
简单请求(GET / POST + 普通 Content-Type):
浏览器 服务器
│ GET /api/data │
│ Origin: http://localhost:3000 →
│ │ 200 OK
│ ← Access-Control-Allow-Origin: http://localhost:3000
│ (缺少该头 → 浏览器拦截响应)
预检请求(PUT / DELETE / 自定义 Header / JSON body 等):
浏览器 服务器
│ OPTIONS /api/data │
│ Origin: http://localhost:3000
│ Access-Control-Request-Method: PUT →
│ │ 204 No Content
│ ← Access-Control-Allow-Origin: ...
│ ← Access-Control-Allow-Methods: GET,POST,PUT
│ ← Access-Control-Max-Age: 3600
│
│ PUT /api/data(实际请求) │
│ Origin: http://localhost:3000 →
CORS 是浏览器行为,Postman / curl 等工具不受影响。
方案一:@CrossOrigin(方法 / 类级别)
适合只有少数接口需要跨域的场景:
@RestController
@RequestMapping("/api/users")
// 类级别:该 Controller 下所有接口都允许跨域
@CrossOrigin(origins = "http://localhost:3000")
public class UserController {
// 方法级别:覆盖类级别配置
@CrossOrigin(
origins = {"http://localhost:3000", "https://app.example.com"},
methods = {RequestMethod.GET, RequestMethod.POST},
allowedHeaders = {"Authorization", "Content-Type"},
maxAge = 3600
)
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) { ... }
}方案二:全局 CORS 配置(推荐)
通过 WebMvcConfigurer 统一配置,适合大多数前后端分离项目:
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**") // 匹配路径
.allowedOrigins(
"http://localhost:3000", // 开发环境
"https://app.example.com" // 生产环境
)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH")
.allowedHeaders("*") // 允许所有请求头
.exposedHeaders("X-Total-Count") // 暴露给前端的响应头
.allowCredentials(true) // 允许携带 Cookie
.maxAge(3600); // 预检缓存时长(秒)
}
}
allowCredentials(true)与allowedOrigins("*")不能同时使用,需指定具体域名。
多环境域名配置
@Configuration
@RequiredArgsConstructor
public class CorsConfig implements WebMvcConfigurer {
@Value("${cors.allowed-origins}")
private String[] allowedOrigins;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(allowedOrigins)
.allowedMethods("*")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}# application-dev.yml
cors:
allowed-origins:
- http://localhost:3000
- http://localhost:5173
# application-prod.yml
cors:
allowed-origins:
- https://app.example.com环境与 Profile 详见 环境与Profile。
方案三:CorsFilter(与 Spring Security 配合)
WebMvcConfigurer 的跨域配置在 Spring Security 过滤器之后执行,当 Security 拦截了预检请求(OPTIONS)时会导致失效。正确做法是注入 CorsFilter:
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of(
"http://localhost:3000",
"https://app.example.com"
));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
config.setAllowedHeaders(List.of("*"));
config.setExposedHeaders(List.of("Authorization", "X-Total-Count"));
config.setAllowCredentials(true);
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}Spring Security 中开启 CORS
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource())) // 启用 CORS
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 预检请求直接放行
.anyRequest().authenticated()
);
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("http://localhost:3000"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
}Spring Security 详见 安全。
三种方案选型
| 场景 | 推荐方案 |
|---|---|
| 个别接口跨域 | @CrossOrigin |
| 全局跨域,不用 Spring Security | WebMvcConfigurer.addCorsMappings |
| 全局跨域 + Spring Security | CorsFilter Bean 或 http.cors() |
常见问题
1. 预检请求被 401 拦截
原因:Spring Security 对 OPTIONS 请求进行了认证检查。
// 修复:显式放行所有 OPTIONS 请求
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// 或:在 CorsFilter 中统一处理,优先级高于 Security2. Cookie 无法携带
原因:allowCredentials 未设置,或前端未开启 withCredentials。
// 后端
config.setAllowCredentials(true);
config.setAllowedOrigins(List.of("http://localhost:3000")); // 不能用 *
// 前端 axios
axios.defaults.withCredentials = true;3. 自定义响应头前端无法读取
默认情况下,浏览器只暴露部分标准响应头,自定义头需显式暴露:
config.setExposedHeaders(List.of("X-Total-Count", "X-Page-Number"));// 前端才能读取到
const total = response.headers['x-total-count'];4. 开发环境用代理替代 CORS
前端开发时,可通过 Vite / webpack-dev-server 代理转发请求,完全绕过 CORS:
// vite.config.ts
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
}此方案只适用于开发环境,生产环境仍需服务端正确配置 CORS。
相关链接
- 安全 — Spring Security 与 CORS 的集成配置
- 过滤器与拦截器 —
OncePerRequestFilter实现自定义 CORS 过滤器 - 环境与Profile — 多环境 CORS 域名配置
- MVC —
WebMvcConfigurer接口总览