Spring Security 集成指南:避免 CORS 跨域问题
在现代 Web 应用开发中,前后端分离架构已成为主流。当我们使用 Spring Security 保护后端 API 时,经常会遇到跨域资源共享(CORS)问题。这篇文章将详细解析 Spring Security 与 CORS 的关系,以及如何正确配置以避免这些常见问题。
什么是 CORS?
CORS(Cross-Origin Resource Sharing,跨域资源共享)是一种安全机制,用于限制浏览器中运行的 Web 应用访问不同源的资源。当前端应用(如 http://localhost:8080
)尝试请求不同源的 API(如 http://localhost:9000/api
)时,浏览器会实施同源策略限制。
"同源"意味着协议、域名和端口都必须相同。例如:
http://example.com/api
和http://example.com/admin
是同源的http://example.com
和https://example.com
不是同源的(协议不同)http://example.com
和http://api.example.com
不是同源的(域名不同)http://example.com
和http://example.com:8080
不是同源的(端口不同)
为什么 Spring Security 会影响 CORS?
当我们集成 Spring Security 时,可能会遇到这样的情况:普通的跨域配置生效了,但受 Security 保护的接口仍然报跨域错误。这是因为:
- 过滤器链顺序问题:Spring Security 的过滤器通常在 CORS 过滤器之前执行
- 预检请求被拦截:跨域复杂请求会先发送 OPTIONS 预检请求,这些请求可能被 Security 拦截
- 安全头部干扰:Security 添加的一些安全相关头部可能与 CORS 头部冲突
Spring Security CORS 问题的典型表现
当遇到 Security 相关的 CORS 问题时,浏览器控制台通常会显示类似以下错误:
Access to XMLHttpRequest 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.
或者:
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
正确配置 Spring Security CORS 的关键步骤
1. 在 Security 配置中启用 CORS
最重要的一点是在 SecurityConfig 中显式启用 CORS 支持:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and() // 启用 CORS 支持
.csrf().disable()
// 其他配置...
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
2. 正确处理预检请求
OPTIONS 请求需要特别关注,因为它们是跨域机制的关键部分:
@Override
public void configure(WebSecurity web) throws Exception {
// 可以考虑完全忽略 OPTIONS 请求的安全检查
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
3. 避免 CORS 配置冲突
如果你同时在多处配置了 CORS(如 WebMvcConfigurer 和 SecurityConfig),确保配置一致:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 这里的配置应与 SecurityConfig 中的一致
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
4. 使用 CorsFilter 并设置高优先级
将 CorsFilter 设置为高优先级,确保它尽早执行:
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOriginPatterns(Arrays.asList("*"));
// 其他配置...
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
常见陷阱和解决方案
1. allowCredentials 与通配符冲突
当 allowCredentials
设置为 true
时,不能使用 *
作为 allowedOrigins
:
// 错误做法
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowCredentials(true);
// 正确做法
configuration.setAllowedOriginPatterns(Arrays.asList("*")); // 使用模式匹配
// 或者指定具体的域名
configuration.setAllowedOrigins(Arrays.asList(
"http://localhost:3000",
"https://your-production-domain.com"
));
configuration.setAllowCredentials(true);
2. JWT 认证与 CORS
使用 JWT 认证时,确保认证过滤器正确处理 OPTIONS 请求:
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
// OPTIONS 请求直接放行
if (request.getMethod().equals("OPTIONS")) {
chain.doFilter(request, response);
return;
}
// 正常的 JWT 验证逻辑...
}
3. 响应头问题
确保 Access-Control-Expose-Headers
包含你需要在前端访问的自定义头:
configuration.setExposedHeaders(Arrays.asList(
"Authorization", "X-Custom-Header", "token"
));
调试 CORS 问题的技巧
- 检查浏览器网络面板:查看 OPTIONS 请求的响应状态和头部
- 使用 curl 测试:绕过浏览器直接测试 API 端点
- 添加日志:在过滤器链中添加日志,查看请求处理顺序
- 使用 Spring Boot DevTools:启用详细日志查看请求处理过程
总结
正确配置 Spring Security 和 CORS 的关键点:
- 在 Security 配置中显式启用 CORS
- 确保对 OPTIONS 预检请求的正确处理
- 避免多处 CORS 配置冲突
- 遵循 CORS 规范限制(如 allowCredentials 与通配符的限制)
- 确保过滤器顺序正确