Spring Security 集成指南:避免 CORS 跨域问题

发布于:2025-05-18 ⋅ 阅读:(24) ⋅ 点赞:(0)

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/apihttp://example.com/admin 是同源的
  • http://example.comhttps://example.com 不是同源的(协议不同)
  • http://example.comhttp://api.example.com 不是同源的(域名不同)
  • http://example.comhttp://example.com:8080 不是同源的(端口不同)

为什么 Spring Security 会影响 CORS?

当我们集成 Spring Security 时,可能会遇到这样的情况:普通的跨域配置生效了,但受 Security 保护的接口仍然报跨域错误。这是因为:

  1. 过滤器链顺序问题:Spring Security 的过滤器通常在 CORS 过滤器之前执行
  2. 预检请求被拦截:跨域复杂请求会先发送 OPTIONS 预检请求,这些请求可能被 Security 拦截
  3. 安全头部干扰: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 问题的技巧

  1. 检查浏览器网络面板:查看 OPTIONS 请求的响应状态和头部
  2. 使用 curl 测试:绕过浏览器直接测试 API 端点
  3. 添加日志:在过滤器链中添加日志,查看请求处理顺序
  4. 使用 Spring Boot DevTools:启用详细日志查看请求处理过程

总结

正确配置 Spring Security 和 CORS 的关键点:

  1. 在 Security 配置中显式启用 CORS
  2. 确保对 OPTIONS 预检请求的正确处理
  3. 避免多处 CORS 配置冲突
  4. 遵循 CORS 规范限制(如 allowCredentials 与通配符的限制)
  5. 确保过滤器顺序正确