본문 바로가기

Backend/SpringBoot

Spring Boot에서 CORS 적용해보기

안녕하세요! 이번 포스팅에서는 CORS가 무엇인지 간단하게 알아보고, Spring Boot에서 CORS를 적용하는 방법에 대해 알아보겠습니다.

전체 코드는 Github에서 확인이 가능합니다. ✍️


저는 만들어둔 API 서버로 클라이언트에서 통신을 시도할 때, 콘솔에 아래와 같은 오류 메세지를 마주친 경험이 있습니다.

❌ Access to XMLHttpRequest at '~' from origin '~' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

부랴부랴 구글링을 해서 검색해보니, 'CORS'를 적용하면 되는데, 아래 어노테이션을 추가하면 해당 현상이 해결된다고 나와 있어서 그대로 추가를 해주었습니다.

@CrossOrigin("*") // 모든 요청에 접근 허용

하지만 위 방법은 모든 요청에 대해 접근을 허용하기 때문에 보안적으로 절대 좋은 방법이 아니라고 생각합니다. 그래서 CORS가 무엇인지, 스프링에서 어떻게 CORS를 설정하는지에 대해 포스팅을 해보려고 합니다. 👀


📚 개념 정리 & 사전준비

1. SOP(Same-origin policy)

CORS에 대한 개념을 이해하기 전에 SOP이 무엇인지를 알아야합니다.

SOP란 같은 Origin에만 요청을 보낼 수 있게 제한하는 보안 정책을 의미합니다.

Origin은 아래와 같은 구성으로 이루어져 있습니다.

  • URI Schema(ex. http, https)
  • Hostname(ex. localhost, naver.com)
  • Port(ex. 80, 8080)

이 중에 하나라도 구성이 다르면 SOP 정책에 걸리기 때문에 ajax 요청을 보낼 수 없습니다.

 

http://www.example.com/dir/page.html에 요청을 보낼 때 예시입니다.

예시 출처 : wikipida ( https://en.wikipedia.org/wiki/Same-origin_policy )

 

2. CORS(Cross-Origin Resource Sharing)

CORS를 한마디로 정의하면 서로 다른 Origin끼리 요청을 주고 받을 수 있게 정해둔 표준이라고 할 수 있습니다. Spring에서는 @CrossOrigin이라는 어노테이션을 지원해주기 때문에, 이 어노테이션을 사용하면 간단하게 CORS를 사용할 수 있습니다.

CORS의 동작원리나 더 자세한 설명은 이 포스팅에서 더 자세하고 다루고 있으니 참고하시면 좋을 것 같습니다!

3. 사전 준비

  • 의존성 추가
    • spring-boot-starter-web
    • spring-boot-starter-thymeleaf

💻 구현 - CORS 적용 전

1. 프로젝트 2개 생성 후 설정

서로 다른 포트에서 접근을 해야하기 때문에 프로젝트를 2개 생성해줍니다. 저는 각각 cors-test-1과 cors-test-2로 프로젝트를 생성해주었습니다. cors-test-2의 의존성에는 thymeleaf를 추가해주어야합니다.

 

2. cors-test-1

application.properties

server.port=1000

포트를 다르게 설정하기 위해 cors-test-1의 port를 1000으로 바꿔주도록 하겠습니다.

 

GreetingController.java

@RestController
public class GreetingController {

  @GetMapping("/hello")
  public String hello() {
    return "안녕하세요?";
  }
}

/hello에 접근하면 안녕하세요?를 리턴하는 컨트롤러 메소드를 만들어줍니다.

 

3. cors-test-2

CorsController.java

@Controller
public class CorsController {

  @GetMapping("/api/view")
  public String view() {
    return "/cors";
  }
}

ajax 요청을 보내기 위해 뷰를 리턴해주는 컨트롤러 메소드를 만들어줍니다.

 

cors.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script>
  $(function(){
    $.ajax("http://localhost:1000/hello")
    .done(function(msg){
      alert(msg);
    })
    .fail(function(){
      alert("fail");
    })
  })
</script>
</body>
</html>

ajax로 cors-test-1 프로젝트의 /hello로 http 요청을 보내는 코드를 작성해줍니다.

 

4. 구현 확인

이제 1차적인 구현은 끝났으니 프로젝트 두 개의 서버를 모두 켜주고, localhost:8080/api/view로 접속하면 fail이라는 정보가 담긴 alert창이 뜨며 콘솔에는 CORS 관련 오류가 나타나는 것을 확인할 수 있습니다. (만약 404 오류가 뜬다면 thymeleaf를 의존성에 추가했는지 다시 한 번 확인해주세요!)


💻 구현 - CORS를 적용하는 여러가지 방법들

이제 방금 나온 오류를 해결하기 위해 Spring에서 제공해주는 어노테이션인 @CrossOrigin을 사용해서 CORS를 적용해보겠습니다.

1. @CrossOrigin 추가하기

cors-test-1/GreetingController.java

@CrossOrigin(origins = "http://localhost:8080") // 추가
@RestController
public class GreetingController {

  @GetMapping("/hello")
  public String hello() {
    return "안녕하세요?";
  }
}

@CrossOrigin은 Spring에서 CORS를 적용할 수 있게 만든 어노테이션입니다. origins 속성으로 도메인을 설정하면 해당 도메인은 다른 Origin을 가지고 있지만 요청을 주고 받을 수 있게 됩니다.

 

확인을 해보면 콘솔창에 에러도 나지 않고 정상적으로 응답을 받아온 것을 확인할 수 있습니다!

 

2. WebConfig에 CORS 설정하기

만약 특정 도메인으로 오는 요청에는 모두 CORS를 적용하고 싶으면 WebConfig를 설정하면 됩니다.

cors-test-1/WebConfig.java

@Configuration
public class WebConfig implements WebMvcConfigurer {

  @Override
  public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping("/**")
        .allowedOrigins("http://localhost:8080");
  }
}

WebMvcConfigurer를 상속 받아서 addCorsMappings를 Override하면 CORS 관련 설정이 가능합니다. 저는 http://localhost:8080에서 오는 요청은 어느 url이든 CORS를 적용해주도록 설정해주었습니다. 이렇게 설정을 하면 따로 @CrossOrigin 어노테이션을 붙이지 않아도 됩니다.

결과 역시 똑같이 나오는 것을 확인할 수 있습니다.

 

3. Proxy 만들기

서버단은 CORS 위반을한다고 서버에서 응답을 막는 것이 아닌,

  1. 클라이언트 - 서버에서 호출
  2. 서버에서 응답
  3. 브라우저에서 CORS 정책을 위반하면 응답 파기

의 플로우로 흘러가게 됩니다. 즉, 요청을 보내는 쪽에서 프록시 서버를 만들어 간접적으로 전달하면 응답을 받을 수 있습니다.

 

cors-test-2/CorsController.java

@Controller
public class CorsController {

  ...

  @GetMapping("/api/proxy")
  @ResponseBody
  public String proxy() {
    String url = "http://localhost:1000/hello";

    RestTemplate restTemplate = new RestTemplate();
    return restTemplate.getForObject(url, String.class);
  }
}

클라이언트와 같은 포트에서 api를 만든 후에, RestTemplate의 getForObject(요청 url, 반환 클래스)를 이용해서 서버에 요청을 보내고 응답을 리턴해줍니다.

 

그 후에 서버를 재가동하고 localhost:8080/api/proxy로 접근하면 결과가 제대로 나오는 것을 확인할 수 있습니다. 위 방법은 클라이언트에서 프록시 서버를 필요로 할 때 사용하는 것이 좋습니다!

 


✨ 정리

분명 예전에 학교에서 배웠던 내용들인데 기억이 잘 안나서 더듬더듬 기억을 되짚어가고 이곳저곳 찾아보며 이해한 내용을 바탕으로 정리를 해보았습니다 😅 혹시 글을 읽으면서 잘못된 내용이 있으면 댓글로 알려주시면 감사하겠습니다!

읽어주셔서 감사합니다! 😊


👏 참고