You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 21:30:55