웹 개발을 혼자 해봤거나 아니면 처음 해보는 사람들끼리 프로젝트를 진행해본적이 있다면 한 번쯤은 꼭 CORS 에러를 마주칠 수 있다. 이게 진짜 악독한 이유는 우리는 따로 잘못적은 로직이 없다. 또한 진짜 악질인 경우가 403에러가 뜰 때이다.
웹 개발을 진행할 때 400번대 에러는 클라이언트, 500번대 에러는 서버쪽 문제라는 얘기를 들어봤을 수도 있을 것이다. 그래서 아무것도 모르는 프론트엔드는 400번대 에러를 마주하고 한참이나 시간을 버리고 백엔드랑 얘기를 나눌 수도 있다.

의 줄일말로 CORS는 한 도메인 또는 Origin의 웹 페이지가 다른 도메인을 가진 리소스에 액세스 할 수 있게하는 보안 메커니즘이다.
좀 더 상세하게 얘기해보자면 한 서버에 API 를 요청하거나 기타 등등의 요청을 진행할 때 서버에서 허용해준 프로토콜,도메인 주소, 포트 번호, 요청 방식 등등 이외에 요청을 보낼 경우 에러를 내보낸다.
예시) https://google.co.kr:8080 을 예시로 들자면 https는 프로토콜, google.co.kr 은 도메인, 8080은 포트 번호이다.
옛날 같은 경우에는 같은 서버에서 프론트도 개발을 하였고 오픈 API 라는게 별로 없었다. 하지만 최근에는 둘이 따로 개발하고 배포하는게 트렌드이고 오픈 API를 공유하는 경우도 많아지기 때문에 CORS가 부각되고 있습니다.
또한 해당 정책이 없다면 아무 곳에서나 민감한 정보에 접근하거나 특정 API를 통해 악의적인 행동이 가능할 수 있다.
예시) CORS 권한을 모두 허용해준 경우


위에는 실제로 대학교 졸업 작품에서 특정 조에서 배포를 진행했는데 CORS 권한을 모든 도메인, 프로토콜, 포트에 허용을 해주도록 코드를 짰고 api 를 GitHub를 통해 공개해서 좋아요가 보내질까 했는데 진짜 보내지는 걸 볼 수 있었다.
이런 경우는 귀여운 경우이고 민감한 유저 정보나 개인 정보등에 접근할 수 있게 되면 그게 진짜 곤란한 경우이니 CORS 는 어떻게 보면 당연히 필요한 존재이다.
보내는 방식에는 크게 3가지 방식이 있다. 우리는 그 중 두 가지만 살펴볼 예정이다.
의미 그대로 단순히 요청을 보내는 방법이다. 특정 조건 하에 예비 요청을 보내지 않더라도 바로 본 요청을 보내는 경우이다.
서버에 바로 본요청을 보낸 뒤, 서버는 헤더에 담겨있는 위의 값들을 붙혀서 보내주면 위의 필터에 걸러지지 않는다면 정상적으로 요청이 간다.

아래와 같은 조건을 만족할 때 자동적으로 Simple request 로 간다.
GET, HEAD 요청Content-Type 헤더가 다음과 같은 POST 요청application/x-www-form-urlencodedmultipart/form-datatext/plainAccept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width를 제외한 헤더를 사용하면 안된다.하지만 우리가 개발하는 REST API의 경우 Content-Type 으로 application/json 을 자주 사용하기에 위와 같은 방식은 잘 이루어지지 않는다.
서버로 바로 요청을 보내는 것이 아닌 해당 요청이 유효하게 작동하는지 먼저 확인하기 위해 OPTION 요청을 먼저 보내보는 것이다.

이런 요청을 왜 먼저 보내보냐면 만약 크기가 큰 데이터를 주고 받는다고 했을 때 큰 request 를 그냥 먼저 보내버렸는데 요청이 거절된다면 서버 사용량이 불필요하게 낭비되기 때문에 효율적인 요청을 위해서 위와 같은 방식이 필요하다.
GET POST HEAD 요청Accept, Accept-Language, Content-Language, Content-Type 만 허용Content-Type 헤더는 다음의 값들만 허용application/x-www-form-urlencodedmultipart/form-datatext/plainCORS 의 경우 백엔드에서 해결하는 방법도 있고 프론트엔드에서도 해결할 수 있는 방법 있다. Proxy 서버를 이용해 우회를 하는 방법도 있고 다른 방법도 있는 것으로 알고 있다. 하지만 난 백엔드 개발자 이고, 원래 기본적으로 백엔드에서 설정해준다면 CORS 는 안전하게 잘 넘어갈 수 있기에 백엔드단에서 해결하는 방법을 찾아보고자 한다.
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/*")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
.allowedHeaders("Authorization", "Content-Type", "refresh_token")
.allowCredentials(true)
.maxAge(3600);
}
위에는 내가 진행했던 CORS 정책과 관련해서 테스트 단계에서 설정했던 CORS 설정이다.
위 설정들에 대해 하나하나 설명해주자면
addMaping의 경우 도메인 뒤에 붙는 주소들이다. 위 velog 로 예를 들자면 velog.io 가 도메인이고 이 뒤에 velog.io/read 혹은 velog.io/write 등등 read, write 등을 의미한다. 해당 코드에서는 모든 요청을 허용한 상태이다.
allowedOrigins의 경우 프로토콜, 도메인, 포트 등을 담았다 해당 도메인은 개발 상태에서 프론트엔드가 요청을 로컬에서 테스트하기 위해 http://localhost:3000 으로 설정하였다.
allowedMethods 의 경우 API 요청을 보낼 때 허용되는 방식을 담았다.
allowedHeaders 의 경우 프론트에서 헤더에 담을 수 있는 키값들을 설정한 건데 Authorization 은 유저의 인증 정보, refresh_token 의 경우 토큰 인증을 재발급 하기 위한 토큰 Content-Type 의 경우 request 요청을 보낼 때의 타입 정보들이 들어간다.
allowCredentials 의 경우 쿠키와 세션의 인증정보를 허용할 것인지 말 것인지를 정해주는 것이다. 해당 프로젝트에서는 쿠키에 refreshToken 을 담아줘야 하기에 허용해주었다.
maxAge의 경우 CORS 동작 방식중 Option 요청을 보내 확인하는 과정이 있는데 동일한 요청에 관해서는 해당 시간동안 재요청을 받지 않는 다는 내용이다.
@CrossOrigin(origin="*", allowedHeaders = "*")
@Controller
public class MainController {
@GetMapping(path = "/")
public String main(Model model) {
return "main";
}
}
메소드 레벨 및 글로벌 레벨에서 Spring MVC 애플리케이션에서 CORS를 지원하는 방법이다. 이 어노테이션은 어노테이션이 달린 메소드 또는 타입을 교차 출처를 허용하는 것으로 표시한다.
기본적으로 @CrossOrigin은 모든 출처, 모든 헤더, @RequestMapping 주석에 지정된 Http 메소드에 최대 30분을 허용한다. 어노테이션에 속성 값을 넣어 기본 값을 대체할 수 있다.
allowCredentials 을 true로 설정하는 경우에 쿠키와 같은 정보들을 넘길 수 있는데, 이 때 Origin 을 모두 허용하게 된다면 에러가 발생한다. 이는 보안상의 큰 이슈 때문인데
예를 들어 특정 사용자 A의 인증 정보를 악의적인 사용자 B가 얻게 된다면 B는 A의 인증정보를 이용해서 자기가 만든 개인 사이트에서 특정 사용자 A가 원하지 않은 행동을 할 수 있다.
예를 들어 은행 홈페이지에서 이러한 설정을 했다면, B가 A의 인증정보를 이용해서 자신한테 A의 계좌에서 송금을 보낼 수 있는 것이다.
그래서 이러한 설정은 자체적으로 막혀서 에러가 나게 된다. 이러한 점을 몰랐어서 꼬박 많은 시간을 이 에러 해결에 투자하게 되었었다..
알고나면 별 거 아닐 수 있으니 잘 알고 지혜롭고 슬기롭게 시간을 아끼면서 잘 사용하자..