서버와 통신을 하다 보면, 누구나 한번쯤은 보게 되는 에러메세지가 있다.
빨간 글씨가 가득해서 당황하게 만드는 문구이지만, 사실 이 문구는 이러한 오류를 해결하는것에 대한 해결책을 얘기해주고 있는 메세지라고 보면 된다. 이러한 메세지를 해석하기 위해서는 몇가지 선수적으로 알고 있어야 하는 지식들이 있다.
Origin 이란
Origin 을 해석해보면 출처라는 단어가 나온다. 출처.. 마치 어떠한 데이터가 전송이 될 때 이 데이터는 어디의 출처에서 보내지는 것입니다 라고 하면 출처라는 단어가 조금 와닿을 듯 하다. 브라우저, 프론트엔드 서버, 백엔드 서버 모두 이러한 출처를 가지고 있고, 출처를 확인할 수 있는 방법 중 하나는 실제 사이트의 URL 부분을 확인해보면 가능하다.
URL 의 구성은 아래 예시 처럼 되어있다. (일반적으로)
Http: // www.domain.com:3000/user?query=name&page=2..
위 URL 에서 강조되어 있는 부분을 이루는 구성은 Protocol(http), Host(domain), Port(3000) 이다. 이 3가지 부분이 바로 Origin 이다.
Origin 에 대한 정책들이 필요한 이유
위에서 Origin 에 대하여 확인을 하였고, 점점 CORS 에 다가가고 있다. Origin 이 존재한다는 것은 알겠는데, 이 Origin 왜 정책적으로 구별해서 다루어 져야 하는지 살펴보자.
Origin 에 대한 정책은 크게 Same Origin 정책과 Cross Origin 정책으로 나뉘게 된다. 정책은 어떠한 규정이라고 생각한다면, 용어 그대로 해석하자면 어떠한 상황에서는 같은 출처만을 허용하고, 어떠한 경우에는 다른 출처도 허용하겠다는 규정이 아닐까 미리 짐작을 할 수 있겠다. 실제로도 그러한데,
우선 SOP(Same Origin Policy) 정책의 경우 '동일한 출처에서만 리소스를 공유할 수 있다' 라는 법칙을 적용한다.
이와 대조적으로 COP(Cross Origin Policy) 의 경우 '다른 출처에서도 리소스를 자유로이 공유할 수 있다' 라는 법칙이 적용된다.
이미지나 동영상 등 리소스들을 정책에 따라 다른 출처에서는 상호작용이 불가능할 수 있다는 점이다.
인터넷에서 자유롭게 리소스가 상호작용하면 좋을 수 있겠지만, 이 부분은 보안측면에서 굉장히 취약점을 드러내개 된다.
자바스크립트의 작동의 경우 기본적으로 Same Origin 정책이 적용되고 있다. 즉, 다른 도메인 측에서 자바스크립트로 요청을 하게 되는 경우를 보안상 제한하고 있다. (브라우저는 기본으로 하나의 서버 연결만 허용되도록 설정이 되어있다). 허나 만약에 Cross Origin 정책이 기본적용이 된다면 어떠한 일이 발생을 하게 될까.
어떠한 해커가 우리에게 어떠한 메일을 송부하였고, 그 메일에는 어떤 도메인 사이트가 링크로 걸려있다고 가정해보겠다. 해커는 사용자를 어떠한 방법을 통해서 링크를 클릭하도록 유혹하였고(돈이 빠져나가고 있다던지...) 사용자는 너무 궁금하여 이 링크를 클릭하게 된다. 이렇게 해커의 유혹에 못이겨 링크를 클릭하게 되면, 해커가 몰래 심어넣은 악의적인 자바스크립트가 실행이 되어, 우리쪽 서버에 요청을 할 수 있다. Cross Origin 이라면 도메인이 달라도 언제나 요청이 가능하고 이에 대한 데이터를 수신이 가능하니, 이러한 우리의 데이터를 찾아 해커가 사용하는 사이트로 재차 전송할 수가 있게 된다.
해킹에 취약해진다는 점에서 분명 SOP 정책이 중요하다는 점을 인지하자.
SOP의 경우 아까 살펴본 Origin 이 같다면, 그 뒤에 다른 요소는 다르더라도 관계없이 동일한 출처로 인식한다. 또한 SOP 정책으로 인해 도메인 접근을 차단하는 주체는 프론트엔드 서버가 아니라 브라우저다! 즉, 위의 저 빨간 오류를 발생시키는 것은 브라우저가 하는 것이다.
그렇다고 죄다 잠그고만 있을 수 없기에, CORS 정책이 필요하다
다른 출처의 리소스를 가져오게 되는 일은 번번하게 벌어지게 된다. 그렇기 때문에, 출처가 다르다는 이유 하나만으로 아예 리소스 상호교환을 차단시켜 버리면, 인터넷을 이용하는 이유가 사라져버린다. 그렇기 때문에 몇 가지의 예외 조항을 두고 다른 출처의 리소스 요청이라도 이 조항에 해당할 경우에는 허용하기로 하였고, 그 중 CORS 정책을 지킨 리소스 요청이 있다.
CORS(Cross-Origin Resource Sharing) 이란 문장을 다시 보면, 결국 Cross-Origin 상태에서의 자원 공유를 말하고 있으며, 보안상의 문제와 결합하여 바라볼 때, 원칙상 Cross-Origin 이라면 SOP 정책에 의해 리소스 교환이 불가능하지만, 몇 가지 특정 조건을 만족한다면 가능하게 해주겠다는 정책이다. 이제 왜 맨 위 빨간 오류가 해결책이라 말하는지 알 수 있겠다. 데이터를 교환하고 싶거든 CORS 정책을 지키라는 의미인 셈이다.
그렇다면 어떠한 조건을 갖추어야 하는 것일까?
기본적으로 클라이언트에서 HTTP 메서드에 의해 요청을 서버에 보내게 될 때, header 부분에는 origin 을 같이 보내게 된다.
이렇게 header 를 통해 Origin 을 같이 보내게 되고, 응답에서는 Access-Control-Allow-Origin 을 응답헤더에 같이 보내게 된다.
만일 서버측에서의 응답 header 부분에 Access-Control-Allow-Origin 부분이 브라우저 출처로서 명시가 되어있지 않다면, 브라우저 측에서 CORS 정책의 위반을 기반으로 데이터 교환을 차단시키는 것이다. 즉, 요청시의 Origin 과 비교를 하여 같다면 정상적으로 응답을 받고, 아니라면 CORS 오류를 발생시킨다.
이러한 과정이라면 문제 제기는 브라우저가 하지만 해결과정은 백엔드 서버에서 해야함을 확인 할 수 있다.
예비요청(Preflight Request)
클라이언트에서 서버로 요청을 보낼 때, 바로 요청을 보내는 것이 아니라 그 전 제대로 안전한 요청인지 부터 확인하게 된다. 이 과정을 예비요청이라고 하고, 특이하게 HTTP 메서드가 OPTION 으로 나타나게 된다.
보통 CORS 문제가 발생하면 바로 이 OPTION 부분에서 먼저 발생을 하게 된다. 이 요청을 통해 서버에서 어떤 Origin 을 허용하고 있는지, 어떠한 header 를 허용하고있는지 등등을 먼저 파악한다. 서버가 이에 다 만족하여 응답을 하게 되면, 그제서야 클라이언트는 자바스크립트 요청을 보내게 된다.
이 외 단순요청과 인증요청이 있는데, 인증 요청의 경우 쿠키와 세션을 같이 포함하여 요청을 보내는 것으로 쿠키와 세션 부분을 포스팅 할 때 같이 다뤄보도록 하겠다.
그래서 결론적으로 어떻게 해결지어야 하는가
CORS 정책에 대해서 살펴보았고, 왜 지켜지어야 하는지에 대해서도 살펴보았다. 이제 이를 어떻게 정책에 맞게 해결할 수 있는지를 살펴보도록 하자.
우선 프론트 서버에서 해결하는 방법으론 프록시 방법이 있다. 프록시 미들웨어를 활용해도 되고, 만약 react 를 create-react-app 을 통해 설치하였다면, pakage.json 에 proxy 벡엔드 포트 주소를 설정해주면서 CORS 문제를 해결할 수 있다.
// pakage.json
'proxy' : "http://..."
프록시를 이용하여 해결한다는 것은 기존 CORS 정책에서 지적하는, 브라우저가 백엔드 서버에 직접 요청하는 과정에서 출처가 다른 브라우저가 접근하려 한다면 차단한다는 부분을 응용한 방법이다. 브라우저에서 백엔드 서버에 직접 접근하는것은 정책에 위반되지만, 브라우저에서 프론트엔드 서버, 그리고 프론트엔드 서버에서 백엔드 서버로 접근하는것은 정책상 문제가 없다.
따라서 프록시 설정을 해주게 되면, 브라우저가 요청을 보낼 때, 이를 프론트엔드 서버(같은 포트)가 받아서, 백엔드 서버로 전달하고, 이에 대한 응답을 백엔드서버에서 프론트엔드 서버로 전달한 다음 프론트엔드 서버에서 브라우저로 전달하게 된다. 같은 출처여야 한다는 점에서 프론트엔드 서버와 브라우저는 만족하기 때문에 CORS 문제가 발생하지 않게 된다.
이와 반대로 원래대로 백엔드 서버측에서 Access-Control-Allow-Origin 을 설정해주면서 CORS 를 해결하는 방법이 있다. 직접 백엔드 서버측에서 이를 적어주어도 되지만, 보통은 미들웨어를 활용하곤 한다. Node.js 서버를 예시를 들어서 설명해보겠다.
npm i cors
cors 라는 미들웨어를 통해서 이를 처리할 수 있다. 예시 코드로서 express 코드를 가져오자면
const express = require("express");
const cors = require("cors");
app.use(
cors({
origin: true, // true 로 설정할 시 자동으로 요청을 보내는 포트로 설정이 된다.
credentials: true, // 쿠키를 서버에 전달하기 위해서 꼭 필요하다.
})
);
위 처럼 미들웨어를 설정하면, CORS 문제를 해결할 수 있다. 주의할 점은 origin 부분인데, 개발 시 용이하기 위해 모든 사이트를 허용하기 위하여 '*' 를 하는 경우가 있는데, 이러면 애초에 CORS 정책을 통해 보안을 강화하려는 목적 자체가 상실되기 때문에 조심하자.
서버에 쿠키를 같이 전달하고 싶다면 위처럼 credential : true 를 통해서 설정해주면 된다. 다만 이 부분은 프론트엔드 측에서도 axios.default 설정을 통해서 같이 해주어야 작동을 한다.
해결책의 큰 틀은 동일하다
다양한 미들웨어가 존재할 것이고, 다양한 언어에서 처리가 가능할 것이다. 다만 CORS 정책에 대해 해결하는 방향성 자체는 결국 2가지로 인지하고 있으면 충분할 듯 싶다.
- 브라우저와 프론트엔드 서버가 동일한 포트라는 것을 활용한 프록시 방법
- 직접 서버에서 브라우저의 출처를 허용하여 해결하는 방법
해결 자체는 어찌저찌 한다고 하더라도, 원리를 파악하는것이 먼저라 판단하여 2가지 해결법을 설명하기에 앞서서 CORS 에 대해 다뤄보았다. 앞으로 같은 오류가 발생한다면 당황하지 말고 네트워크 창부터 확인해보자..
참고문헌