웹을 개발하다보면 CORS 정책을 위반했다고 에러가 발생하는 경우가 무척 많다. 웹 개발을 하면서 겪게 되는 CORS에 대해서 더이상 시달리기 싫어 본 글을 작성한다.
CORS는 (CORS : Cross Origin Resource Sharing) 다른 출처 리소스 공유
CORS 가 일어나는 이유는, 웹 브라우저에서 HTTP 요청을 하는 과정에서 각각 어떤 요청을 하느냐에 있기 때문이다. 웹 클라이언트 어플리케이션이 다른 출처의 리소스를 요청할 때는 HTTP 프로토콜을 사용하여 요청을 보내게 된다. 이때 '이 리소스에 접근이 주소지' 를 헤더에 담아 서버와 통신을 하게 되는데, 이 헤더에 들어있는 정보와 정보가 불일치하여 에러가 발생하게 된다.
먼저 웹 생태계에서 리소스 요청을 제한하는 것과 관련한 두가지 정책을 알고가면 더 이해가 잘 될것 같다. 웹에는 크게 2가지 정책을 갖고 있다.
즉, 동일 출처 서버에 있는 자원들은 자유롭게 가져올 수 있지만, 다른출처 서버에 있는 자원들은 가져올 수 없다는 것을 뜻한다.
우리가 다른 출처로 리소스를 요청한다면, SOP 정책을 위반한 것이 되고 SOP의 예외조항인 CORS 정책까지 지키지 않는다면 다른 출처의 리소스를 사용할 수 없게 되는 것이다.
즉, 다른 출처의 리소스를 사용하는 것을 제한하는 행위는 하나만의 정책(ex. SOP)만으로 결정된 사항이 아니다. SOP 에서 정의된 예외 조항과 CORS를 사용할 수 있는 케이스들이 맞물리지 않는 특수한 케이스에만 리소스 요청이 불가능하게 막는 것이다.
위의 정책을 사용하는 이유는 간단하다. 보안성 때문이다. 웹에서 작동하는 Client Application 들은 사용자의 공격에 매우 취약하다. 예를들어 지금 당장 F12 를 눌러 개발자모드를 통해서 해당 HTML의 구조와 리소스의 출처들을 크게 공들이지 않고(?) 열람할 수 있다.
그렇기 때문에 악의를 가진 사용자가 소스코드를 구경한 후 CSRF나 XSS와 같은 방법을 사용해서 어플리케이션에서 코드가 실행된 것처럼 꾸며서 사용자의 정보를 탈취하기가 쉬워져 보안성이 취약해지기 때문이다.
💡 CSRF
사이트 간 요청 위조(또는 크로스 사이트 요청 위조, 영어: Cross-site request forgery, CSRF, XSRF)는 웹사이트 취약점 공격의 하나로, 사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위(수정, 삭제, 등록 등)를 특정 웹사이트에 요청하게 하는 공격을 말한다.
유명 경매 사이트인 옥션에서 발생한 개인정보 유출 사건에서 사용된 공격 방식 중 하나다.
사이트 간 스크립팅(XSS)을 이용한 공격이 사용자가 특정 웹사이트를 신용하는 점을 노린 것이라면, 사이트간 요청 위조는 특정 웹사이트가 사용자의 웹 브라우저를 신용하는 상태를 노린 것이다. 일단 사용자가 웹사이트에 로그인한 상태에서 사이트간 요청 위조 공격 코드가 삽입된 페이지를 열면, 공격 대상이 되는 웹사이트는 위조된 공격 명령이 믿을 수 있는 사용자로부터 발송된 것으로 판단하게 되어 공격에 노출된다.
출처 : WIKI 백과 [CSRF]
우리가 인터넷 브라우저에서 어떤 사이트를 접속할 때 URL을 통해서 접근하게 된다. 가령 네이버를 접속한다고 가정해보자. 그렇다면 다음과 같은 URL을 통해서 접속할 것이다.
1. https://www.naver.com/[querystrings]
2. http://www.naver.com/[querystrings]
웹 브라우저는 사용자가 입력한 URL을 토큰화 하여 연속된 작업을 실행한다. 이때 자원의 출처는 3가지 파트로 나뉜다.
CORS에서 Protocol + Host + PORT 를 자원의 출처(Origin)으로 본다.
Origin 에 대한 판단은 웹 브라우저 마다 상이하다. 가장 대표적인 것이 IE이다. IE는 출처를 비교할 때 Port 부분은 무시한다. 이로 인해 보안에 취약해지게 되었다. (이외에 표준을 따르지 않아서 따로 코드를 넣어 개발을 해야한다는...)
결국 이런 출처의 구분은 WEB브라우저 가 진행한다. 이를 다시말해보면, 서버 간에 통신을 할 때는 정책이 적용되지 않는다는 말도 된다.
즉, 클라이언트단에서 API를 요청하는게 아니라, 서버단에서 다른 출처의 서버로 API를 요청하면 CORS 에러로부터 자유로워 진다. 그래서 이를 이용한 Proxy 서버라는 것이 있다.
앞에서 말했듯이, SOP 정책을 위반해도 CORS 정책에 따르면 다른 출처의 리소스도 허용하여 사용할 수 있다. 그렇다면 CORS의 브라우저 작동과정과 방법들을 살펴보자
일반적인 웹 어플리케이션을 개발할 때 가장 자주 마주치는 시나리오 이다. 이 방법 핵심은 예비요청 과 본요청으로 나누어 서버로 전송한다는 것에 있다.
예비 요청에서 Http의 메소드 중, Option 메소드가 사용되어 본 요청을 보내기 전 브라우저 스스로 이 요청이 안전하지 확인하는 과정을 거친다.
예비요청에서 브라우저는 Access-Control-Request-Header 를 사용하여 자신이 본 요청에서 Content-Type 헤더를 사용할 것을 알려주거나, Access-Control-Request-Method를 사용하여 이후 사용할 메소드를 서버에게 미리 알려준다.
예비요청을 통해서 서버로부터 받은 결과를 통해서, 브라우저는 리소스의 요청을 체크하여 CORS 위반사항을 판단한다.
이 방식은 예비요청없이 본 요청만으로 CORS 정책 위반여부를 검사한다.
즉, 바로 서버가 본 요청의 응답으로 'Access-Control-Allow-Origin' 의 값을 보내주면 그때 브라우저가 CORS 위반여부를 판단하여 검사하는 방식이다.
하지만 이 방식은 아무때나 단순 요청을 사용할 수 있는 것이 아니고, 특정 조건을 만족하는 경우에만 예비요청을 생략할 수 있다. (이 조건을 만족시키기는 다소 까다롭다.)
헤더에 Authorization나, text/html과 application/json 의 컨텐츠 타입을 가지도록 설계되는 시스템이 많기 때문에 거의 사용되지 않는다....
이는 인증된 요청을 사용하는 방법이다. 이 방식은 CORS의 기본적인 방식이라기 보다는 다른 출처 간의 통신에서 좀 더 보안을 강화하고 싶을 때 사용하는 방법이다.
기본적으로 브라우저가 제공하는 비동기 리소스 요청 API인 XMLHttpRequest 객체나 fetch API는 별도의 옵션 없이 브라우저의 쿠키 정보나 인증과 관련된 헤더를 함부로 요청에 담지 않는다. 이때 요청에 인증과 관련된 정보를 담을 수 있게 해주는 옵션이 바로 'credentials' 옵션이다.
이 옵션에는 총 3가지의 값을 사용한다.
두서가 길었다. CORS 정책 위반 문제를 해결하는 가장 대표적인 방법은 기본적으로 서버에서 Access-Control-Origin 헤더에 알맞은 값을 세팅해 주는 것이다.
이 헤더는 Nginx나 Apache 같은 서버 엔진의 설정에서 추가할 수도 있고, 소스 코드내에서 응답 미들웨어 등을 사용하여 세팅할 수도 있다.
CORS를 가장 많이 마주치는 환경은 로컬에서 FE 어플리케이션을 개발하는 경우일 것이다.
BE에는 이미 'ACAO(Access-Control-Allow-Origin)' 헤더가 세팅되어 있겠지만, 이 중요한 헤더에다가 'https://localhost:3000' 과 같은 흔한 출처를 넣어주는 경우는 없다.
FE개발자는 대부분 Webpack과 webpack-dev-server를 이용하여 자신의 환경에 개발환경을 구축하는데, 이 라이브러리가 제공하는 프록시를 사용하여 CORS 정책을 우회할 수 있게 된다.
이는 /api 로 시작하는 URL로 보내는 요청에 Proxy를 사용하여 {target} 으로의 프록싱을 해준다. 따라서 CORS 정책을 지킨 것처럼 브라우저를 속이면서(?) 서버와 통신이 가능해진다.
위키백과 : CSRF
https://ko.wikipedia.org/wiki/%EC%82%AC%EC%9D%B4%ED%8A%B8_%EA%B0%84_%EC%9A%94%EC%B2%AD_%EC%9C%84%EC%A1%B0
https://ko.wikipedia.org/wiki/%EB%8F%99%EC%9D%BC-%EC%B6%9C%EC%B2%98_%EC%A0%95%EC%B1%85