1. 为什么SpringSecurity需要处理跨域问题
跨域问题本质上是浏览器出于安全考虑实施的同源策略限制。当我们在前后端分离架构中开发应用时,前端可能运行在http://localhost:8080,而后端API部署在http://api.example.com,这种协议、域名或端口不同的情况就会触发跨域限制。
SpringSecurity作为一个安全框架,默认会启用CSRF防护等安全机制,这些机制与跨域请求存在天然冲突。比如:
- 预检请求(Preflight)会被SpringSecurity过滤器链拦截
- 携带凭证(Credentials)的请求需要特殊CORS配置
- 自定义头部信息可能触发安全规则
实际案例:某电商网站前端调用支付接口时,因为缺少正确的CORS配置,导致OPTIONS预检请求返回403,支付流程中断。排查发现是SpringSecurity的CSRF防护拦截了预检请求。
2. SpringSecurity中的CORS配置方案
2.1 基础配置方式
最简单的CORS配置是通过@CrossOrigin注解:
java复制@RestController
@RequestMapping("/api")
public class MyController {
@CrossOrigin(origins = "http://frontend.com")
@GetMapping("/data")
public ResponseEntity<String> getData() {
return ResponseEntity.ok("Secure data");
}
}
但这种方案存在明显局限:
- 需要每个控制器方法单独配置
- 无法与SpringSecurity的安全策略深度集成
- 不支持动态配置允许的源
2.2 全局CORS配置
更推荐的方式是在Spring配置类中定义全局CORS规则:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://frontend.com")
.allowedMethods("GET", "POST")
.allowCredentials(true)
.maxAge(3600);
}
}
关键参数说明:
allowedOrigins: 可替换为allowedOriginPatterns支持通配符allowCredentials: 控制是否允许携带cookie等凭证maxAge: 预检请求缓存时间(秒)
2.3 与SpringSecurity集成
当项目引入SpringSecurity后,必须额外配置才能使CORS生效:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and()
.authorizeRequests()
// 其他安全配置...
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://frontend.com"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", configuration);
return source;
}
}
这种配置方式确保了:
- CORS过滤器在安全过滤器链中正确位置
- 安全策略不会意外拦截合法跨域请求
- 可以精细控制每个端点的CORS规则
3. 生产环境中的进阶配置技巧
3.1 动态源管理
实际项目中,允许的源可能需要动态变化:
java复制@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(List.of("*")); // 谨慎使用
configuration.setAllowedMethods(List.of("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", configuration);
// 动态源解析
source.setCorsConfigurationResolver(request -> {
CorsConfiguration config = new CorsConfiguration(configuration);
String origin = request.getHeader("Origin");
if (isAllowedOrigin(origin)) { // 自定义校验逻辑
config.setAllowedOrigins(List.of(origin));
}
return config;
});
return source;
}
3.2 与CSRF防护的配合
当需要同时启用CORS和CSRF防护时:
java复制http.cors(withDefaults())
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringAntMatchers("/api/public/**")
);
关键点:
- CSRF Token需要通过Cookie或Header传递
- 公开API可以排除CSRF防护
- 确保CORS配置允许携带CSRF Token
3.3 性能优化建议
- 预检请求缓存:设置合理的
maxAge减少OPTIONS请求 - 精确的路径匹配:避免
/**这种宽泛匹配 - 响应头过滤:移除不必要的CORS头减少带宽
java复制configuration.setExposedHeaders(List.of("Content-Disposition"));
configuration.setMaxAge(1800L); // 30分钟缓存
4. 常见问题排查指南
4.1 预检请求失败
典型症状:浏览器控制台报错"Response to preflight request doesn't pass access control check"
排查步骤:
- 检查SpringSecurity过滤器链顺序
- 确认没有自定义过滤器过早返回响应
- 使用
curl -v -X OPTIONS [URL]测试预检请求
4.2 凭证丢失问题
当前端设置withCredentials: true但后端未配置时:
javascript复制fetch('http://api.example.com/data', {
credentials: 'include'
});
后端必须响应:
Access-Control-Allow-Credentials: true- 不能使用
allowedOrigins: ["*"]
4.3 特殊场景处理
文件下载场景:
java复制@GetMapping("/download")
public ResponseEntity<Resource> downloadFile() {
// 设置Content-Disposition头
String headerValue = "attachment; filename=\"example.pdf\"";
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, headerValue)
.header(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.CONTENT_DISPOSITION)
.body(fileResource);
}
iframe嵌入问题:
除了CORS配置外,还需要设置:
X-Frame-Options头Content-Security-Policy的frame-ancestors指令
5. 安全最佳实践
-
不要盲目允许所有源:
java复制// 危险配置! configuration.setAllowedOrigins(List.of("*")); configuration.setAllowCredentials(true); // 与通配符源冲突 -
严格限制HTTP方法:
java复制// 只允许必要的方法 configuration.setAllowedMethods(List.of("GET", "POST")); -
敏感头信息保护:
java复制// 明确指定允许的请求头 configuration.setAllowedHeaders(List.of("Authorization", "Content-Type")); // 明确指定暴露的响应头 configuration.setExposedHeaders(List.of("X-Custom-Header")); -
定期审计CORS配置:
- 检查是否有过度宽松的规则
- 验证允许的源是否仍然有效
- 监控异常跨域请求日志
-
结合其他安全机制:
java复制http.cors(withDefaults()) .headers(headers -> headers .contentSecurityPolicy(csp -> csp .policyDirectives("default-src 'self'") ) .frameOptions().deny() );
在实际项目中,我曾遇到一个典型案例:开发环境配置了宽松的CORS规则,但忘记在生产环境收紧,导致潜在的安全风险。后来我们通过自动化配置检查解决了这个问题:
java复制@Profile("!prod")
@Bean
CorsConfigurationSource devCorsSource() {
// 开发环境宽松配置
}
@Profile("prod")
@Bean
CorsConfigurationSource prodCorsSource() {
// 生产环境严格配置
}
这种环境区分配置既保证了开发便利性,又确保了生产安全。记住,CORS配置本质上是安全配置的一部分,必须给予足够重视。
