Spring Boot CORS配置问题:React前端请求返回401未授权
问题描述
React前端(http://localhost:3000)请求Spring Boot接口http://localhost:52700/greeting-javaconfig时,触发CORS拦截报错:
Access to fetch at 'http://localhost:52700/greeting-javaconfig' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status。
同时React端返回401未授权,但使用curl测试能正常得到200 OK响应,调整多种CORS配置后问题仍未解决。
现有代码问题分析
- CORS配置冲突:混合使用Servlet(WebMvcConfigurer)和Reactive(CorsWebFilter)两种CORS配置,且自定义CorsFilter未正确处理OPTIONS预检请求。
- Security配置混乱:同时使用WebSecurityCustomizer忽略所有请求、SecurityFilterChain定义认证规则,且混用Reactive的UserDetailsService与Servlet的HttpSecurity,导致认证逻辑冲突。
- @CrossOrigin注解冲突:控制器上的@CrossOrigin与全局CORS配置重复,可能导致规则覆盖。
- React请求配置缺失:未设置
credentials: 'include',无法携带认证凭证,且Accept头与接口返回类型不匹配。
解决方案步骤
1. 统一Servlet栈CORS配置
删除Reactive相关CORS代码,保留全局WebMvc配置,确保支持凭证携带:
package com.example.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("http://localhost:3000") // 明确指定前端域名,不要用*(需携带凭证时) .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) // 允许携带认证凭证 .maxAge(3600); // 预检请求缓存时长 } }
删除自定义的CorsFilter(实现WebFilter的类),避免重复配置。
2. 修复Security配置(统一Servlet栈)
调整SecurityFilterChain,启用CORS和Basic Auth,处理预检请求的认证问题:
package com.example.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .cors() // 启用全局CORS配置 .and() .authorizeHttpRequests(auth -> auth .anyRequest().authenticated() // 所有请求需认证 ) .httpBasic() // 启用Basic Auth .authenticationEntryPoint(new Http403ForbiddenEntryPoint()) // 避免OPTIONS预检请求返回401 .and() .csrf().disable(); // 跨域场景下禁用CSRF return http.build(); } @Bean public UserDetailsService userDetailsService() { UserDetails user = User .withUsername("user1") .password("{noop}user1Pass") .roles("USER") .build(); return new InMemoryUserDetailsManager(user); // 使用Servlet栈的用户服务 } }
说明:Http403ForbiddenEntryPoint会让未认证的请求返回403而非401,避免浏览器因401触发额外的认证弹窗,同时CORS配置会处理OPTIONS请求的跨域权限。
3. 调整React请求代码
确保携带凭证,匹配接口返回类型:
const url = 'http://localhost:52700/greeting-javaconfig'; const username = 'user1'; const password = 'user1Pass'; const credentials = btoa(username + ':' + password); const options = { method: 'GET', headers: { 'Authorization': 'Basic ' + credentials, 'Content-Type': 'application/json', 'Accept': 'application/json', // 接口返回JSON,匹配Accept头 'cache-control': 'no-cache' }, credentials: 'include' // 携带跨域凭证 }; fetch(url, options) .then(response => { if (!response.ok) { throw new Error(`请求失败,状态码:${response.status}`); } return response.json(); }) .then(data => console.log('响应数据:', data)) .catch(error => console.error('请求错误:', error));
4. 移除控制器上的@CrossOrigin注解
删除BaseController中的@CrossOrigin(origins = "http://localhost:3000/"),避免与全局CORS配置冲突。
内容的提问来源于stack exchange,提问作者Meyer Buaharon

