아마 프론트엔드 개발자라면 한번쯤은 위의 에러를 본적이 있을 것이다.
이는 CORS에러라고 하는 것인데 본격적으로 CORS를 알기 전에 사전지식으로 SOP란 무엇인지 알아보도록 하겠다.
Same Origin Policy
다른 출처의 리소스를 사용하는 것에 제한하는 보안 방식
URL의 Protocol
, Host
, Port
를 통해 같은 출처인지 다른 출처인지 판단할 수 있다.
즉, 셋 중에 하나라도 문자열이 다르다면 다른 출처라고 판단하는 것이다.
단, IE는
Port
가 달라도 같은 출처라고 함
만약 선량한 사용자가 특정 서비스에 로그인을 하면 인증 토큰을 받게 된다.
그 후 만약 해커가 사용자에게 누를 수 밖에 없는 링크를 보내서 만약 사용자가 해당 링크를 클릭하게 된다면 해커가 만든 주소로 이동을 하는데 해커가 만약 해당 페이지에 사용자가 사용하는 특정 서비스에 나는 바보다라는 게시글을 등록하게 하는 스크립트가 작성되어 있는 상태라면 해당 스크립트가 실행되게 되는데 현재 사용자는 특정 서비스의 인증 토큰을 갖고 있는 상태이다.
그렇다면 해커는 그 인증 토큰으로 서비스에 접근하여 나는 바보다라는 게시글을 등록하라고 명령을 하게 된다.
여기서 SOP가 위력을 발휘하는데 특정 서비스에서 Origin
을 확인한다.
이 요청이 어디서 온건지 확인하고 다른 출처에서 왔다(COR)고 판단하면 SOP에 위반된다라고 하며 해당 요청을 받아들일 수 없다라고 알려준다.
즉, SOP는 동일 출처에서의 Request
만 받아들이기 때문에 보안에 있어 큰 강점을 보여준다.
하지만 또 문제가 있다, 다른 출처의 리소스가 필요한 경우가 있는데 이 때 사용하는 것이 CORS이다.
Cross Origin Resource Sharing
다른 출처의 자원을 공유
CORS는 추가 HTTP 헤더를 사용하여 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에게 알려주는 체제
본 요청을 보내기 전 서버한테 물어보는 Request
만약 친구의 집에 놀러가야하는 상황이라고 생각해보자 친구에게 놀러가기 전에 놀러가도 되는지 확인하지 않는다면 큰 민폐일 수 있다.
때문에 친구에게 물어보고 가야할텐데 이 같은 작업이 Preflight Request
라고 생각하면 된다.
OPTION
메서드를 통해 다른 도메인의 리소스에 요청이 가능한지 확인한다.요청을 하게 되면 2번의 요청이 보내지게 된다.
처음에는 Prefligth Request
즉, 해당 요청을 보내도 되는지 확인하고 Actual Request
를 보낸다.
만약에 Preflight Request
가 거부됐다면 Actual Request
는 실제로 보내지는 않는다.
Preflight Request
를 할 때 물어볼 사항들이 있고 거기에 맞는 포맷이 있다.
OPTION /doc HTTP/1.1
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
맨 처음 헤더에는 Origin
이 들어있어야 한다.
즉, 이 요청을 어디서부터 날라 가는거다 라고 표현을 해야하고 다음 Access-Control-Request-Method
는 실제 요청의 메서드 즉, 나는 이 메서드를 보낼건데 이 메서드를 보내도 되는지 물어보는 것이고 Actual Request
의 메서드를 알려줘야 합니다.
이렇게 클라이언트가 요청을 보내면 서버에서 답변이 올텐데 이는 아래처럼 생겼다.
Access-Control-Allow-Origin: http:foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Access-Control-Allow-Origin
은 서버측에서 해당 Origin
은 허가가 되어있다고 보내주는 것이다.Access-Control-Allow-Methods
는 해당 Method
들이 허가 되어있다고 알려주는 것이다.Access-Control-Allow-Headers
는 해당 Header
들이 허가 되어있다고 알려주는 것이다.Access-Control-Max-Age
는 응답 캐시기간을 의미한다.Preflight Response
가 가져야 하는 특징
Preflight Request
와는 다르게 바로 본 요청을 보내며CORS
인지 확인하는 요청방식
GET
, POST
, HEAD
메서드를 사용해야한다.Content-Type
이 아래 세가지 중에 하나여야 한다.위와 같은 상황이라면 요청을 보낸 클라이언트의 Origin
과 서버에서 허가하는 Origin
이 다르니 cross origin
에러가 발생할 것이다.
그렇다면 여기서 의문이 든다. 한번만 요청하기 때문에 자원이 절약되는데 굳이 Preflight Request
를 사용하는 이유는 무엇일까?
CORS를 모르는 서버를 위해
만약 Simple Request
를 CORS
를 모르는 서버에게 보낸다고 생각해보자
클라이언트는 브라우저를 통해 특정 메서드로 요청을 보낼텐데 서버는 CORS
를 모르기 때문에 일단 요청을 처리하고 응답하게 되는데 당연히 응답해야 할 Access-Control-Allow-Origin
이 없을 것이다.
그렇다면 그제서야 브라우저가 서버는 Access-Control-Allow-Origin
이 없기 때문에 cross-origin
에러를 발생시킵니다.
여기서 문제점은 서버가 일단 요청을 처리한다는 것이다. 위의 예제는 GET
메서드니 큰 일은 없겠지만 PATCH
나 DELETE
같은 데이터의 변경이 일어나는 메서드라면 큰 문제가 된다. CORS
에러가 발생했음에도 이미 서버는 요청을 모두 처리했기 때문에 지우면 안되는 데이터까지 수정하게 되기 때문이다.
하지만 Preflight Request
를 사용하게 된다면 해당 요청은 사전 요청이기 때문에 서버는 어떠한 행동도 취하지 않는다.
때문에 서버가 안전하게 요청을 주고 받을 수 있다.
여러 해결방법이 있지만 주로 사용하는 Proxy
서버를 설정해서 해결하는 방법을 주로 다룰 것이다.
해당 해결방법은 Front Server
에서 특정 query string
으로 요청을 보내면 포트를 살짝 바꿔서 보내는 방식이다.
아래의 예제는 Webpack dev-server
를 사용한다면 사용해볼 수 있는 설정이다.
proxy: {
'/todos': {
target: 'http://localhost:7500/todos',
pathRewrite: { '^/todos': '' }
}
}
Plugin 이나 Iframe 에서 프록시서버가 필수적인 경우가 있습니다. 이때는 https://cors.sh 와 같은 프록시 서비스를 이용할수 있습니당 :)