프로젝트 내내 시달렸던 CORS 에러, 대체 무엇일까?
PalmSpring을 하면서 가장 힘들었던 순간이 언제냐고 물어보면
나는 CORS 에러가 터지는 매 순간이라고 대답하고 싶다.
그만큼 정말 공부하고 싶었고, 일주일의 쉬는 기간이 주어진 김에 시작하는 CORS 에러 정복
CORS
,
웹 개발을 처음 해보신 분들은 무조건 거쳐가는 관문이다. 물론 나도
나는 CORS
를 약 6월달 해커톤에서 처음으로 마주했고,
이후 PalmSpring
이라는 큰 프로젝트에서 다시 한번 마주하게 되었다.
그 당시에도 생전 처음보는 코드에 꽤나 당황해서 여기저기 도움을 요청했던 기억이 난다...🥹
CORS
를 이해하기 위해서는 발생의 기원부터 들어가보려고 한다.
이CORS
라는 것은SOP
라는 정책을 어기는 경우 발생하게 된다. 이SOP
란 무엇일까?
이는 Same-Origin-Policy
라는 뜻으로 2011년에 RFC 6454 에 등장하게 된 보안 정책 방식입니다.
여기서 RFC란? RFC에 대해 궁금하실까봐..
사실 내가 궁금해서 찾아봄Request-For-Comments 라는 뜻으로 국제 인터넷 표준화 기구에서 관리하는 기술 표준으로
승인되는 경우 RFC 뒤에 일련의 번호가 붙은채로 불리게 됩니다.CORS는 다만 이 정책이 나오기 전에 등장했다고 합니다 신기하죠잉
말을 인용하자면
known colloquially as the "same-origin policy". Although this
security model evolved largely organically, the same-origin policy
can be understood in terms of a handful of key concepts. This
section presents those concepts and provides advice about how to use
these concepts securely.3.1. Trust
The same-origin policy specifies trust by URI. For example, HTML
documents designate which script to run with a URI:
SOP 즉, 동일 출처 정책은
URI를 기준으로 신뢰성을 파악하고 있다고 나와있습니다.
그렇다면 위를 어겼을때 나오는 CORS란 무엇일까요?
Cross Origin Resource Sharing 의 줄임말입니다.
단어 그대로, 서로 다른 Origin 간의 자원 교환이 일어나는 것을 의미하는데
예시를 하나 들어서
백엔드는 8080 PORT
를 기본으로 하는 Spring Boot
로 개발하고
프론트에서는 3000 PORT
를 기본으로 하는 React
로 개발했다고 가정해보았을때
이때 이 Origin의 접근이 허가받지 않은 접근 즉,
Access-Control-Allow-Origin에 들어가 있지 않은 Origin일 경우 CORS Error가 발생하게 됩니다.
간단한 예시는 이렇고 실제는 Port 보다는 좀 더 복잡한데
여기서 Origin과 URI에 대해서 알아보고자 합니다.
여기서 Origin이란, 특정 페이지에 접근할 때 사용되는 Url의
Scheme(프로토콜)
, Host(도메인)
, Port(포트)
을 말하는데
여기서, Same-Origin 이란 이 3가지가 일치할 때 이고
이 3가지 중 하나라도 다르면 Cross-Origin 이라고 불리게 됩니다.
개발자들이 CORS 에러를 마주하고 해결방식을 찾아보면,
방법들이 정말 많이 나오는데 그 방법이 다 가지각색이라 정말 당황스러웠습니다.🤧
제가 그랬습니다.
이유는 CORS 자체가
이 3가지의 동작방식이 있기 때문인데,
첫번째 Simple Request
의 동작방식일 때는 몇가지 조건 사항이 있습니다.
이때는 다른 것들은 하지 않고, 단순히 아래와 같은 코드를 작성해
위에서 설명한 Origin 만 비교한 채 접근을 허용하게 됩니다.
Access-Control-Allow-Origin : https://foo.example
다만, 보통은 Content-Type을 application/json 으로 보내는 경우가 다수 이기 때문에
이때는 위와 같은 방식이 아닌 Preflight Request
로 동작하게 되는데요.
이때는 Pre
, 미리 flight
날린다.
라는 이름에 걸맞게 브라우저가 본 요청을 바로 보내는 것이 아닌, 안전한지 검증하기 위한
예비 요청 즉 Preflight Request
를 먼저 보내고
이때 브라우저가 Options
메서드를 사용하여 Header에
실제 요청에 사용할 Method와 Headers를 담아서 보내게 됩니다.
이후 서버에서
"Access-Control-Allow-Origin", "https://test.palmsummer.site"
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Methods","*");
httpResponse.setHeader("Access-Control-Max-Age", "3600");
httpResponse.setHeader("Access-Control-Allow-Headers",
이와 같이 허용을 해주는데
이는 F12를 통해 응답 헤더에서 확인할 수도 있습니다.
3번째로 Credentialed Request는
쿠키와 토큰 같이 자격인증 정보 즉, Credentials를 같이 전송할때 발생하게 됩니다.
이때는 서버에서 헤더에
Access-Control-Allow-Credentials
를 true로 설정해주고 보내줘야 하는데
다만 인증정보를 보낸 다는 점에서 기존에서 사용하던
와일드 카드 (*)의 사용을 브라우저가 막게 됩니다.
이 점 때문에 꽤나 삽질했습니다.^^...
"Access-Control-Allow-Origin", "https://test.site"
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Methods","POST, GET, DELETE, PUT");
httpResponse.setHeader("Access-Control-Max-Age", "3600");
httpResponse.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization" )
예를들어 이와 같이
정확히 명시해서 보내줘야 한다는 겁니다.
자 이제 다 끝났니? 이제 개발을 하자
CORS 에러를 우리 팀은 Filter을 이용하여 구현했었다.
정말 여기 나와있는 모든 방법을 다 해봤던것 같다 눈물 좔좔
다만, 다들 알겠지만 굳이 Filter가 아니더라도 CORS를 상대할 수 있는 방법은 많다는 사실!
각자의 서비스에 어울리는 방법을 생각하고 고민해보는 개발이 되었으면 좋겠다.
물론 나도....
그럼 여기까지!
좋은 글 감사합니다. 자주 올게요 :)