Spring Boot特定控制器CORS错误排查与解决求助
Spring Boot特定控制器CORS跨域错误排查与解决
我在Spring Boot应用中遇到CORS跨域错误,仅TodoController控制器出现该问题,其余控制器(如AuthController)正常工作。已尝试在控制器上添加@CrossOrigin("*")注解,也配置过全局CORS规则,但问题依旧。
报错的控制器代码(TodoController)
import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.CrossOrigin; import com.sinandemir.todoapp.dto.requests.TodoRequest; import com.sinandemir.todoapp.dto.responses.TodoResponse; import com.sinandemir.todoapp.services.TodoService; @RestController @RequestMapping("api/v1/todos") @CrossOrigin("*") public class TodoController { private TodoService todoService; public TodoController(TodoService todoService) { this.todoService = todoService; } @PreAuthorize("hasRole('ADMIN')") @PostMapping public ResponseEntity<TodoResponse> addTodo(@RequestBody TodoRequest todoRequest) { TodoResponse savedTodo = todoService.addTodo(todoRequest); return new ResponseEntity<TodoResponse>(savedTodo, HttpStatus.CREATED); } @PreAuthorize("hasAnyRole('ADMIN','USER')") @GetMapping("{todoId}") public ResponseEntity<TodoResponse> getTodoById(@PathVariable Long todoId) { TodoResponse todo = todoService.getTodo(todoId); return new ResponseEntity<TodoResponse>(todo, HttpStatus.OK); } @PreAuthorize("hasAnyRole('ADMIN','USER')") @GetMapping public ResponseEntity<Page<TodoResponse>> getAllTodosWithPagination(Pageable pageable) { Page<TodoResponse> todos = todoService.getAllTodosWithPagination(pageable); return new ResponseEntity<Page<TodoResponse>>(todos, HttpStatus.OK); } @PreAuthorize("hasRole('ADMIN')") @PutMapping("{todoId}") public ResponseEntity<TodoResponse> updateTodo(@RequestBody TodoRequest todoRequest, @PathVariable Long todoId) { TodoResponse todo = todoService.updateTodo(todoRequest, todoId); return new ResponseEntity<TodoResponse>(todo, HttpStatus.OK); } @PreAuthorize("hasRole('ADMIN')") @DeleteMapping("{todoId}") public ResponseEntity<String> deleteTodo(@PathVariable Long todoId) { todoService.deleteTodo(todoId); return new ResponseEntity<String>("todo deleted successfully.", HttpStatus.OK); } @PreAuthorize("hasAnyRole('ADMIN','USER')") @PatchMapping("{todoId}") public ResponseEntity<TodoResponse> changeCompletedStatus(@PathVariable Long todoId) { TodoResponse todo = todoService.changeCompletedStatus(todoId); return new ResponseEntity<TodoResponse>(todo, HttpStatus.OK); } }
正常工作的控制器代码(AuthController)
import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.sinandemir.todoapp.dto.requests.RefreshTokenRequest; import com.sinandemir.todoapp.dto.requests.UserLoginRequest; import com.sinandemir.todoapp.dto.requests.UserRegisterRequest; import com.sinandemir.todoapp.dto.responses.UserLoginResponse; import com.sinandemir.todoapp.dto.responses.UserRegisterResponse; import com.sinandemir.todoapp.services.AuthService; import com.sinandemir.todoapp.services.RefreshTokenService; import io.jsonwebtoken.ExpiredJwtException; @RestController @RequestMapping("api/v1/auth") @CrossOrigin("*") public class AuthController { private AuthService authService; private RefreshTokenService refreshTokenService; public AuthController(AuthService authService, RefreshTokenService refreshTokenService) { this.authService = authService; this.refreshTokenService = refreshTokenService; } @PostMapping("register") public ResponseEntity<UserRegisterResponse> register(@RequestBody UserRegisterRequest registerRequest) { UserRegisterResponse user = authService.register(registerRequest); return new ResponseEntity<UserRegisterResponse>(user, HttpStatus.CREATED); } @PostMapping("login") public ResponseEntity<UserLoginResponse> login(@RequestBody UserLoginRequest loginRequest) { UserLoginResponse user = authService.login(loginRequest); return new ResponseEntity<UserLoginResponse>(user, HttpStatus.OK); } @PostMapping("refresh-token") public ResponseEntity<String> refreshToken(@RequestBody RefreshTokenRequest refreshTokenRequest) { try { String newJwtToken = authService.refreshAccessToken(refreshTokenRequest.getRefreshToken()); if (newJwtToken != null) { return new ResponseEntity<String>(newJwtToken, HttpStatus.CREATED); } else { return new ResponseEntity<String>("Token refresh failed.", HttpStatus.BAD_REQUEST); } } catch (ExpiredJwtException e) { refreshTokenService.deleteRefreshTokenByRefreshToken(refreshTokenRequest.getRefreshToken()); return new ResponseEntity<String>("Session has expired.", HttpStatus.UNAUTHORIZED); } } }
Spring Security配置类
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import com.sinandemir.todoapp.security.JwtAuthenticationEntryPoint; import com.sinandemir.todoapp.security.JwtAuthenticationFilter; @Configuration @EnableMethodSecurity public class SecurityConfig { private UserDetailsService userDetailsService; private JwtAuthenticationEntryPoint authenticationEntryPoint; private JwtAuthenticationFilter authenticationFilter; public SecurityConfig(UserDetailsService userDetailsService, JwtAuthenticationEntryPoint authenticationEntryPoint, JwtAuthenticationFilter authenticationFilter){ this.userDetailsService = userDetailsService; this.authenticationEntryPoint = authenticationEntryPoint; this.authenticationFilter = authenticationFilter; } @Bean public static PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf((csrf) -> csrf.disable()) .authorizeHttpRequests((authorize) -> { authorize.requestMatchers("api/v1/auth/**").permitAll(); authorize.anyRequest().authenticated(); }).httpBasic(Customizer.withDefaults()); http.exceptionHandling(exception -> exception.authenticationEntryPoint(authenticationEntryPoint)); http.addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean public AuthenticationManager manager(AuthenticationConfiguration configuration) throws Exception{ return configuration.getAuthenticationManager(); } }
尝试过的全局CORS配置类
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration @EnableWebMvc public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("http://localhost:5173") .allowedMethods("*") .allowedHeaders("*"); } }
前端触发错误的请求代码
export const getTodosWithPagination = async ( token: string, page: number, size: number ) => { try { const data = await axios.get(BASE_URL + `todos?page=${page}&size=${size}`, { headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}`, }, }); const response = data.data; return response; } catch (error) { if (axios.isAxiosError(error)) { if (error.response?.status === 401) { throw new Error("Geçersiz token."); } if (error.response?.status !== 201) { throw new Error("Bilinmeyen bir hata!"); } } else return error; } };
React中触发请求的useEffect代码
useEffect(() => { const token = cookies.auth?.accessToken const getTodos = async () => { if (token) { const response = await getTodosWithPagination(token, 0, 10) console.log(response); } return null } getTodos() }, [])
问题解决方法
问题根源在于Spring Security拦截了跨域请求的OPTIONS预检请求。对于需要携带自定义头(如Authorization)的跨域请求,浏览器会先发送OPTIONS请求确认服务器是否允许,而原Security配置中未放行这类请求,导致预检失败。
修改SecurityFilterChain配置,添加放行所有OPTIONS请求的规则:
@Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf((csrf) -> csrf.disable()) .authorizeHttpRequests((authorize) -> { authorize.requestMatchers("api/v1/auth/**").permitAll(); authorize.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll(); // 新增:放行所有OPTIONS预检请求 authorize.anyRequest().authenticated(); }).httpBasic(Customizer.withDefaults()); http.exceptionHandling(exception -> exception.authenticationEntryPoint(authenticationEntryPoint)); http.addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); }
注意:修改后需要确保引入org.springframework.http.HttpMethod包。
内容的提问来源于stack exchange,提问作者msdemir
相关产品推荐
相关产品推荐

