CORS

📅 TIL #153




🎯 Achievement Goals

CORS란?
CORS 접근 제어 시나리오
CORS 에러 사례






Intro.

예전에 코드스테이츠 수강할 당시에 CORS에 대해서 블로깅을 한 적이 있었다. 하지만… 그냥 개념 정도만 블로깅한 수준이였다.. (처참하다..)

이번에는 CORS 개념에 대해 다시 한 번 되짚어보고, 더 자세하게 다뤄볼 예정이다. 그리고 실제로 프로젝트에서 사용했을 때 CORS에러를 많이 만났었는데 왜 만났는지, 어떻게 해결했는지 적어볼 예정이다!






CORS란?


CORS에 대해서 알아보기 전에, 왜 CORS가 나오게 되었는지부터 알면 앞으로 이해하는데 많은 도움이 된다.


HTTP 요청은 기본적으로 Cross-Site HTTP Requests가 가능하다. 즉, img 태그로 다른 도메인의 이미지 파일을 가져오거나, link 태그로 다른 도메인의 CSS를 가져오거나, script 태그로 다른 도메인의 Javascript 라이브러리를 가져오는 것이 가능하다.


하지만 로 둘러싸여 있는 스크립트에서 생성된 Cross-Site HTTP Requests는 Same Origin Policy를 적용 받기 때문에 Cross-Site HTTP Requests가 불가능하다고 한다.


그래서 에서 생성되는 XMLHttpRequest에 대해서도 Cross-Site HTTP Requests가 가능해야 한다는 요구에 따라 CORS가 등장하게 되었다.



그렇다면 CORS란 도대체 뭘까?


교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)은 추가 HTTP header를 사용해서 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제이다.

웹 애플리케이션은 리소스가 자신의 출처(도메인, 프로토콜, 포트)와 다를 때 Cross-origin 요청을 실행한다.

-MDN-


CORS 접근 제어 시나리오


CORS에도 요청의 종류가 있는데, 총 3가지가 있다.

  1. Simple Request
  2. Preflight Request
  3. Credentialed Request (인증 정보 포함 요청)


Preflight Request


preflightRequest


Preflight Request부터 알아보자면 사전 요청 작업이라고 이해하면 더 좋을 것 같다. CORS는 다른 출처의 접근 권한을 알려주는 체제라고 했는데, 다른 출처에 접근하기 전에 실제 요청을 보내도 안전한지 판단하기 위해 하는 작업이라고 생각하면 된다.


위 그림으로 보면 actual 요청 전에 Preflight Request를 통해 인증 헤더를 전송하여 서버의 허용 여부를 미리 체크하는 예비 요청이다.


그렇다면 Preflight Request는 서버에게 매번 두번의 요청을 보내게 된다. 그러면 트래픽이 불필요하게 증가할 수 있는데, 이때 서버의 헤더 설정으로 캐싱이 가능하다. HTTP의 OPTIONS 메서드인 Access-Control-Max-Age를 통해 Preflight 응답 캐시 기간을 설정할 수 있다.


응답 캐시 기간을 통해 해당 기간 동안은 Preflight Request를 하지 않을 수 있다.


이 외에는 Access-Control-Request-*를 통해 원하는 설정 값을 넣어주면 된다.

Access-Control-Allow-Origin: 접근 가능한 url 설정 (https://github.com/useonglee)

Access-Control-Allow-Headers: 접근 가능한 헤더 설정 (content-type)

Access-Control-Allow-Methods: 접근 가능한 Http Method 설정 (GET, POST)


Preflight Response는 상태 코드가 200이어야 하며, body는 비어있는 것이 더 좋다.



Simple Request


Simple Request는 Preflight Request과 비교해 보았을 때, 사전 요청의 유무 차이라고 볼 수 있다. 하지만 사전 요청이 없는 대신에, 몇 가지 조건을 모두 만족해야 한다.


  • GET, POST, HEAD 중의 한 가지 방식을 사용해야 한다.
  • POST 방식일 경우 Content-type이 아래 셋 중의 하나여야 한다.
    A. application/x-www-form-urlencoded
    B. multipart/form-data
    C. text/plain
  • 헤더는 Accept, Accept-Language, Content-Language, Content-type만 허용.



Credentialed Request (인증 정보 포함 요청)


Credentialed Request는 인증 관련 헤더를 포함할 때 쓰는 헤더이다. 만약 인증을 위해 사용되는 토큰을 클라이언트에서 자동으로 담아서 보내고 싶을 때 credentials를 include하면 서버 측에 전달이 된다.


하지만 서버도 마찬가지로 서버측에서도 Access-Control-Allow-Credentials: true 설정을 해줘야지만 사용이 가능하다.
(Access-Control-Allow-Origin: * 과 같이 사용하면 안된다.)


CORS 요청은 기본적으로 Non-Credential 요청이므로, xhr.withCredentials = true 를 지정하지 않으면 Non-Credential 요청이다.




CORS 에러 사례


프로젝트를 하면서 직접 만났던 CORS 에러를 간단하게 기록해보려고 한다.


나는 프로젝트 당시에 내 컴퓨터에서 백엔드 분들이 git에 코드를 업데이트 하면 그 코드를 내 로컬로 옮겨서 서버를 실행시키고 프론트 개발 작업했다.


하지만 작업을 하다가 백엔드 분들이 최신 버전을 git에 올렸을 경우, 나는 그것을 인지하지 못하면 서버 파일을 최신화 시키지 않고 작업을 하게 된다. 이때 CORS 에러를 처음 만나게 되었다.


또 다른 경우도 있었는데, 개발 단계에서는 서버를 24시간 가동시키지 않기 때문에, 백엔드 개발자분들에게 서버 상황을 피드백 받지 않고 작업을 하게 되면 CORS 에러를 보는 상황이 발생했다.


서버가 제대로 작동되고 있는지, 서버 코드가 업데이트 되었는지에 대해서 항상 백엔드 개발자분들과 많은 커뮤니케이션이 오고 가지 않으면 CORS 에러를 자주 맛 볼 것이다.


프로젝트를 하면서 느낀점은 코드를 잘 짜는 것도 중요하지만 더 중요한 것은 커뮤니케이션이였다고 생각한다. 그렇게 나는 한 두번 CORS 에러를 만나본 뒤로는 만나지 않을 수 있었다.





Outro.


웹 개발자라면 기본 상식인 CORS에 대해서 다시 한 번 되짚어 보는 계기가 되었고, 프로젝트 경험을 살리면서 블로깅할 수 있어서 좋았다.

이렇게 블로깅을 하면 CORS 개념에 대해 다시 인지하고 싶을 때 언제든 쉽게 찾아볼 수 있어서 좋은 것 같다.