[Network] CORS(Cross-origin Resource Sharing)

Node.js 프로그래밍을 하며 만났던 오류중에 cors 관련해서 오류가 발생한 적이 있었다. 따라서 구글링을 했고, cors 미들웨어를 사용하면 문제가 해결된 경험이 있었다. 그때는 그렇게 간략히(?) 보내줬지만 이번에는 정리해보려고 한다.

CORS(Cross-origin Resource Sharing) 란?

CORS를 알기 위해 알아야 할 것들이 있다. 차근차근 알아보자.

출처(Origin)

→ URL의 프로토콜, 호스트, 포트를 통해 출처를 파악할 수 있다.

http://localhost:3030/api/user?id=1234#wallet
  • http: 프로토콜
  • localhost: 호스트
  • 3030: 포트

→ 즉, 위 3개로 출처를 파악한다는 뜻이다.

  • api/user: 경로
  • ?id=1234: 쿼리스트링
  • wallet: 프래그먼트 = 리소스 자체의 다른 부분을 가리키는 앵커. 앵커는 북마크의 한 종류, 해당 위치의 컨텐츠를 보여주기 위한 방법. HTML 문서 상에서는 해당 위치로 스크롤이 된다. → 당장 이 블로그만 봐도 On This Page의 목차를 누르면 프래그먼트가 생기는 것을 볼 수 있다.

origin.jpeg

개발자 도구를 열어서 확인해보자!

SOP(Same-origin Policy) 란?

SOP란 Same Origin Policy의 준말로, 다른 출처의 리소스를 사용하는 것에 대해 제한하는 방식! 즉, 같은 출처의 리소스만 사용하는 정책을 말한다.

SOP는 왜 생겼을까?

사실 원래 SOP가 특별한 것이 아니라, 보안상의 이슈로 다른 출처의 리소스 요청이 들어오면 개인정보 유출 등, 불법적인 요청으로 간주했다고 한다. 즉, 잠재적으로 해로울 수 있는 다른 출처에서 가져온 리소스와 상호작용 하는 것을 제한하여 공격 경로를 줄여주는 것이다.

하지만 웹 사이트에서 할 수 있는 일이 많아지면서, 보안 정책 때문에 불편한 일이 점점 늘어갔다. 예를 들어, 내가 제작하는 웹에서 지도 API를 사용하는 작업이 필요하다면? 다른 출처의 리소스가 필요한 상황이 되는 것이고, SOP를 위반하게 된다. 이와 같이 SOP를 통해 오히려 제한이 걸리는 상황이 많아졌고, 위와 같은 요구는 점차 늘어갔기 때문에 다른 출처(cross-origin)에 대한 리소스를 가져올 방법을 연구했다.

JSONP(JSON with Padding)

JSONP는 앞서 설명한 SOP의 보안 이슈로 인해 다른 출처의 리소스를 가져올 수 없기 때문에 이용하는 우회 정책이다. HTML의 스크립트 요소로부터 요청되는 호출에는 보안상 정책이 적용되지 않기 때문에 생겨난 방법이라고 한다.

즉, JSONP는 요청이 아니라

CORS(Cross-origin Resource Sharing) 동작 원리

JSONP는 우회적인 방식이다. 그래서 공식적으로 다른 출처의 리소스를 사용할 수 있게 하기 위해서 등장한 것이 CORS이다.

CORS는 교차 출처 리소스 공유의 줄임말로, 추가 HTTP 헤더를 사용하여 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제이다.

기본적으로는 다음과 같다. 브라우저는 자신의 출처를 HTTP의 요청 헤더에 넣어서 보낸다

Origin: https://akgop.github.io

이후 서버는 응답을 할 때 응답 헤더의 Access-Control-Allow-Origin 이라는 값에 “접근이 허용된 출처” 라고 브라우저에게 알려준다. 이제 브라우저는 Origin과 Access-Control-Allow-Origin을 비교한다. 그리고 유효한지 검사한다. 이는 가장 기본적인 흐름이고 아래 3가지의 예시로 작동한다.

Preflight Request

preflight란 어감에서 느낄 수 있듯이 리소스 요청을 하기 전에 미리 OPTIONS 메소드를 통해 요청을 전송하기에 안전한지 확인한다. cross-site 요청은 유저 데이터에 영향을 줄 수 있기 때문에 미리 검증하는 단계를 거치도록 하는 것이다.

한 번에 하면 되지 굳이 왜?

CORS spec이 만들어지기 이전에 서버들은 브라우저의 SOP만 가능하다는 전제하에 설계가 되었다. 하지만 CORS를 통해 cross-site request가 가능하지며 기존 서버들은 이에 대한 보안 이슈를 핸들할 수 있는 방침이 없기 때문에 이와 같은 서버들을 보호하기 위해 CORS는 preflight request를 포함하게 되었다. 따라서 OPTIONS 메소드를 통해 서버가 CORS를 핸들링할 수 있는지 먼저 확인을 하고 Access-Control-Allow-Origin에 해당 출처를 반환한다.

preflight.jpeg

위와 같이 동작하는데 여기서 주의깊게 봐야 하는 점은 200 Ok를 받고, Origin과 Access-Control-Allow-Origin을 비교하여 둘의 출처가 다르다면 CORS 오류가 발생할 수 있다. 즉, 200패킷을 받아도 오류가 발생한다는 것이다.

단순 요청(Simple Request)

simple request는 예비 요청없이 본문에 바로 요청부터 해보고, 서버가 응답 헤더에 Access-Control-Allow-Origin을 넣어주면 그 때 브라우저가 비교하여 여부를 검사하는 방법이다.

하지만 아무때나 이 방법을 사용할 수는 없다. MDN Mozilla에 의하면 다음과 같은 조건에만 가능하다고 한다.

  • 메소드가 GET, POST, HEAD인 경우
  • Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width를 제외한 헤더를 사용하면 안된다.
  • 만약 Content-type을 사용하는 경우에는 application/x-www-form-urlencoded, multipart/form-data, text/plain 만 허용된다.

대부분의 HTTP, REST API는 application/json 타입을 갖도록 설계가 되므로, 위 조건을 만족시키는 경우는 드물다. 즉, 잘 안쓰인다고 한다!

Credentialed Request

해당 내용은 나중에 추가로 알아봐야겠다!