CORS

김영민·2022년 2월 13일
0
post-thumbnail

교차 출처 리소스 공유라고 번역되는 CORS(Cross-Origin Resource Sharing)는 쉽게 말해 다른 출처에서 오는 리소스를 허용 할지 말지 결정하는 것을 뜻한다.
여기서 잠시 다음 주제로 넘어가기 전에 출처에 대해 간단히 설명하고 넘어가겠다.

출처는 Scheme, Host, Port 이렇게 3가지로 구성되어있다.

https://www.google.com:433http://localhost:8080을 예로 들면 아래와 같다고 볼 수 있다.
Scheme : https://, http://
Host : www.google.com, localhost
Port : :433, :8080

그리고 출처가 같다는 의미는 이 Scheme, Host, Port가 모두 같아야 함을 의미한다.


다른 출처에서 리소스를 받는 다는게 어떤 의미인가?

웹은 기본적으로 SOP(Same Origin Policy, 동일 출처 정책)를 기반으로 하는 보안 정책을 갖고 있었다.
하지만 웹이라는 환경이 오픈 스페이스이다 보니 다른 출처에 있는 리소스를 가져와야 하는 경우가 많아졌다.
예를들어, 검색 api를 이용하여 기능을 넣을 때 라던지 frontendbackend가 협업해서 개발을 할 때도 서로 다른 로컬(즉, 서로 다른 출처)에서 작업이 이뤄지기 때문에 나중에 front와 back을 연결할 때 이 SOP 문제가 발생한다.
따라서, 이 문제를 해결하기 위해 몇 가지 예외조항만 지킨다면 출처가 달라도 리소스를 허용해주는 CORS 정책이 등장한 것이다.


CORS 정책 작동원리

CORS는 서버가 아닌 브라우저에서 결정되는 정책이다. 따라서 브라우저에서 리소스를 요청한 방식이 CORS 정책을 위반했을 때, 브라우저에서는 CORS 에러가 발생하지만 서버에서는 상태코드가 200으로 정상적으로 나타난다는 것을 명심해야 한다. 브라우저의 요청을 받은 서버는 요청대로 문제 없이 리소스를 응답한 것이고, CORS 에러는 브라우저가 결정한 것이기 때문이다.


브라우저에서 출처 확인하는 방법

개발자도구 -> 네트워크를 보면 응답헤더에는 Access-Control-Allow-Origin이 있고, 요청헤더에는 origin이 있음을 확인할 수 있다.
origin은 요청했을 때 리소스가 어느 출처에서 오는지를 알려주는 값이고,
Access-Control-Allow-Origin은 응답 받을 때 리소스를 허용해주는 출처 값이라고 보면 된다.
브라우저는 이 두 개의 값을 비교해서 같으면 허용하고, 다르면 CORS 정책 위반이라는 에러를 띄운다.

그리고 브라우저가 이 두개의 값을 비교하는 방법으로는 총 3가지가 있다.

1. Preflight Reqeust(예비요청 방식)

브라우저가 서버에게 요청을 보낼 때 예비요청을 보낸 후 본요청을 보내는 방식이다.
- 브라우저가 예비요청을 할 때 미리 origin을 헤더에 담아서 서버에게 보내면 서버가 Access-Control-Allow-Origin과 같은 정보를 헤더에 담아서 보내준다. 그럼 브라우저가 예비요청 단계에서 originAccess-Control-Allow-Origin를 비교하여 CORS 정책을 위반했는지 안했는지 판단한다. 정책 위반을 하지 않았다면 바로 본 요청을 보내 서버로부터 리소스를 받아온다.

  • 서버가 브라우저가 보낸 예비요청에 대해 응답할 때는 Access-Control-Allow-MethodsAccess-Control-Request-Headers에 대한 정보도 보내준다.
    • Access-Control-Allow-Methods : 본 요청에서 어떤 메소드(GET, POST, PUT, DELETE 등)를 사용할지 알려준다.
    • Access-Control-Request-Headers : 많은 헤더 정보를 알려주지만 대표적으로 본 요청에서 어떤 Content-Type을 사용할지 알려준다.

2. Simple Request(단순요청 방식)

예비요청 없이 바로 본요청으로 originAccess-Control-Allow-Origin을 비교하는 방식이다.

하지만 단순요청 방식을 사용하기 위해서는 몇 가지 제약이 있다.

  1. 요청하는 method는 GET, POST, HEAD 중 하나여야 한다.
  2. 헤더에는 Accept, Accept-Language, Content-Language, Content-Type만 허용된다.
    (이 외에 들어갈 수 있는 헤더 값이 더 있긴 하지만 현재 수준에서는 이 정도만 살펴보겠다)
  3. 헤더에서 Content-Type을 사용할 경우 application/x-www-form-urlencoded, multipart/form-data, text/plain만 허용된다.

위 제약 중 3번에서 허용되는 Content-Typeapplication/json이 빠져있기 때문에 실질적으로 단순요청 방식을 사용하는 경우는 드물다고 볼 수 있다.


3. Credentialed Request(인증정보요청 방식)

인증정보요청 방식은 헤더에 인증관련 내용을 포함시켜서 다른 출처 간 통신을 할 때 보안을 더 강화하기 위해 사용하는 방식이다.

옵션내용
       same-origin (default)       동일 출처일 때만 인증정보를 전송한다.                
include다른 출처여도 인정정보를 전송한다.
omit인증정보를 절대로 전송하거나 받지 않는다.

여기서 주의할 점은 옵션 값으로 same-origin이나 include를 줬다면 서버에서 Access-Control-Allow-Origin의 헤더 값으로 *(모든 출처허용)을 주어선 안된다. 이렇게 값을 넣을시 CORS 정책을 위반했다는 에러가 뜨기 때문에 반드시 허용할 출처를 명시해줘야 한다.


ps. frontend에서 CORS 정책 에러를 해결하기 위해 webpack-dev-server proxy를 사용하는 경우가 있는데 이는 로컬 환경에서만 작동하고, 배포했을 때는 전혀 제 기능을 못하기 때문에 CORS 정책 에러가 발생하면 꼭 backend의 도움을 받도록 해야 한다.

profile
Macro Developer

0개의 댓글