SpringBoot 3.3.4
JDK 17
React 18.3.1
스프링부트 RESTful 서비스를 이용해 리액트에서 데이터를 조회하려는데 개발자모드 Console창을 보니 다음과 같이 에러가 발생하였다.
Access to fetch at 'http://localhost:8080/api/items' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
번역하면
원본 'http://localhost:3000'에서 'http://localhost:8080/api/items'로의 가져오기 액세스가 CORS 정책에 의해 차단되었습니다: 요청된 리소스에 'Access-Control-Allow-Origin' 헤더가 없습니다. 불투명한 응답이 필요한 경우 요청의 모드를 'no-cors'로 설정하여 CORS를 비활성화한 상태에서 리소스를 가져오세요.
이와 같은 해석대로 프런트엔드에서 API를 호출할 때 mode: 'no-cors'로 설정 후 호출하면 요청이 성공하더라도 데이터를 정상적으로 가져오지 못하는 오류가 발생한다.
그래서 CORS 정책을 풀려면 서버 측에서 처리해야 한다. Spring Boot 백엔드에서 관련 컨트롤러에 @CrossOrigin 어노테이션을 추가하여 CORS를 활성화하거나 Spring Boot 구성에서 전역적으로 구성하면 된다. 이렇게 하면 프런트엔드에서 응답에 올바르게 액세스 할 수 있게 된다.
React의 서버 주소가 http://localhost:3000 이므로 다음과 같이 @CrossOrigin(origins = "http://localhost:3000")를 추가하였다.
@CrossOrigin(origins = "http://localhost:3000")
@RestController
public class ItemController {
@Autowired
private ItemRepository itemRepository;
@GetMapping("/items")
public Iterable<Item> getItems(){
return itemRepository.findAll();
}
}
그런데 위와 같이 처리했음에도 동일하게 에러가 발생하였다.
@CrossOrigin 어노테이션을 추가했음에도 에러가 발생했던 건 application.properties 파일에 base-path 설정한 것 때문이었다.
RESTful 서비스의 base path로 /api를 사용하기 위해 spring.data.rest.base-path=/api를 설정했는데 @CrossOrigin 어노테이션은 이를 해결해주지 못하는 것으로 보인다.
그래서 해당 속성값을 삭제하고 다음과 같이 Controller 파일에 @RequestMapping("/api")를 추가하는 방식으로 변경하였다.
@CrossOrigin(origins = "http://localhost:3000")
@RequestMapping("/api")
@RestController
public class ItemController {
@Autowired
private ItemRepository itemRepository;
@GetMapping("/items")
public Iterable<Item> getItems(){
return itemRepository.findAll();
}
}
더 이상 에러가 발생하지 않고 데이터가 정상적으로 화면에 표시된다.
그런데 spring.data.rest.base-path 설정한 값 그대로 사용하고 싶을 때는 어떻게 해야 할까? 이 때 Spring Boot 구성에서 전역적으로 구성하는 방식을 사용해야 한다.
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
@Configuration
public class RestConfig implements RepositoryRestConfigurer {
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
cors.addMapping("/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowCredentials(true);
}
}
참고로 application.properties 파일에 spring.data.rest.base-path를 설정했을 때와 @RequestMapping 어노테이션을 사용했을 경우 Response의 JSON 형태가 다르다.
spring.data.rest.base-path를 설정했을 때는 객체._ embedded.items 키에서 데이터 접근이 가능하다. React에서 data => setItems(data._embedded.items)
@RequestMapping 어노테이션을 사용했을 경우는 키 없이 리턴된 배열객체에서 데이터를 가져올 수 있다.
React에서 data => setItems(data)
결론
스프링부트의 RESTful 서비스 이용한다면 application.properties 파일에 spring.data.rest.base-path를 사용하고 RepositoryRestConfigurer를 이용한 두 번째 방법으로 처리하는 것이 맞다고 생각된다.