안녕하세요! 이번 포스팅에서는 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에 요청을 보낼 때 예시입니다.
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 위반을한다고 서버에서 응답을 막는 것이 아닌,
- 클라이언트 - 서버에서 호출
- 서버에서 응답
- 브라우저에서 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로 접근하면 결과가 제대로 나오는 것을 확인할 수 있습니다. 위 방법은 클라이언트에서 프록시 서버를 필요로 할 때 사용하는 것이 좋습니다!
✨ 정리
분명 예전에 학교에서 배웠던 내용들인데 기억이 잘 안나서 더듬더듬 기억을 되짚어가고 이곳저곳 찾아보며 이해한 내용을 바탕으로 정리를 해보았습니다 😅 혹시 글을 읽으면서 잘못된 내용이 있으면 댓글로 알려주시면 감사하겠습니다!
읽어주셔서 감사합니다! 😊
👏 참고
'Backend > SpringBoot' 카테고리의 다른 글
Spring Boot에서 Custom Valid Annotation 만들기 (0) | 2020.09.24 |
---|---|
Spring Boot에서 이벤트 사용하기 (6) | 2020.08.27 |
Spring Boot에서 Spring Rest Docs 사용해보기 (4) | 2020.07.24 |
Spring Boot Custom Annotation 만들기 (4) | 2020.07.08 |
Spring Security Error Message 커스텀하기 (9) | 2020.06.02 |