React客户端请求Spring Boot OAuth2资源服务器时出现CORS错误
问题分析与解决方案
你的问题核心是浏览器的OPTIONS预检请求被Spring Security拦截,导致CORS头无法正常返回。Postman不会触发严格的预检请求(或直接携带Token),所以能正常访问;而React作为浏览器端发起的复杂请求(携带Authorization头)会先发送OPTIONS预检,这个请求不带Token,被你的Security配置里的anyRequest().authenticated()规则拦截,返回的响应没有CORS头,从而触发浏览器的CORS错误。
同时,你之前通过WebMvcConfigurer配置的CORS规则不会生效——因为Spring Security的过滤器链执行顺序早于DispatcherServlet(WebMvc的处理入口),预检请求还没到WebMvc的CORS处理环节就被Security拦截了。
修复步骤
修改你的SecurityConfig,在Security层面配置CORS并放行OPTIONS预检请求:
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import java.util.Arrays; import org.springframework.http.HttpMethod; @Configuration public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .cors(withDefaults()) // 启用Security的CORS支持,关联下方的CorsConfigurationSource .authorizeHttpRequests((authz) -> authz .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 放行所有OPTIONS预检请求 .anyRequest().authenticated() ) .oauth2ResourceServer().jwt(); return http.build(); } // 定义全局CORS配置规则 @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); // 建议指定具体允许的源,不要用*(如果需要支持多个源可以添加多个) config.setAllowedOrigins(Arrays.asList("http://localhost:3000")); // 允许的HTTP方法 config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); // 允许的请求头,必须包含Authorization(因为你的请求带Token) config.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type")); // 如果需要传递凭证(如Cookie),开启此项,同时前端请求要设置credentials: 'include' config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; } }
额外说明
- 可以删除原来的
CorsConfig类,现在所有CORS规则都在Security层面配置,避免重复处理。 - 不要使用
allowedOrigins("*")搭配allowCredentials(true),浏览器会禁止这种配置,必须指定具体的源。 - 前端React的fetch请求如果需要带凭证,要添加
credentials: 'include':
fetch('http://localhost:8080/folders', { method: 'GET', headers: { 'Authorization': 'Bearer ' + token }, credentials: 'include' // 按需添加 })
内容的提问来源于stack exchange,提问作者Pokemon1
相关产品推荐
相关产品推荐

