참고 링크
Access to XMLHttpRequest at ‘https://api.com/users’ from origin ‘http://localhost:3000’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
•
웹 개발을 하다보면 흔하게 마주하는 CORS! 서버와의 통신으로 데이터를 받아올 때 주로 등장하는 이 오류 왜 발생하는 걸까?
SOP 와 CORS 란?
•
둘 다 다른 출처의 리소스 요청 (도메인 또는 포트가 다른 서버의 자원을 요청) 에 관련된 보안 정책임.
•
다른 출처로 리소스를 요청한다면 SOP 정책을 위반한 것이 되며, SOP 의 예외 조항인 CORS 정책까지 지키지 않는다면 아예 다른 출처의 리소스를 사용할 수 없음!
SOP(Same-Origin Policy) 란?
•
RFC 6454 에서 처음 등장한 보안 정책으로, 말 그대로 "같은 출처에서만 리소스를 공유할 수 있다"는 규칙을 가진 정책임.
CORS(Cross-Origin Resource Sharing) 란?
•
"교차(다른) 출처 리소스 공유"라고 해석할 수 있음.
•
오픈된 공간인 웹에서 다른 출처에 있는 리소스를 가져오는 일은 굉장히 흔하고, 무작정 막을 수도 없기 때문에 몇 가지 예외 조항을 두고 이 조항에 해당하는 리소스 요청은 출처가 다르더라도 허용하는 규칙을 가진 정책임.
•
우리가 겪는 CORS 관련 이슈는 모두 CORS 정책을 위반했기 때문에 발생하는 것임.
+) 출처(Origin) 는 무엇인가?
•
출처는 위의 형태에서 :80 , :443 과 같은 포트 번호까지 모두 합친 것을 의미함. 즉, 서버의 위치를 찾아가기 위해 필요한 가장 기본적인 것들의 집합을 뜻함.
•
같은 출처와 다른 출처를 구분하는 방법은 간단함. 위와 같은 URL 구성 요소 중 Scheme(Protocol) , Host , Port 3가지가 동일한 경우 같은 출처라고 판단함.
+) 출처에 포트 번호가 명시적으로 포함되어 있는 경우, 포트 번호까지 모두 일치해야 같은 출처라고 인정됨. (포함되어 있지 않다면 고려 대상이 아님)
CORS 가 필요한 이유
•
웹 애플리케이션 (웹 브라우저) 은 공격에 매우 취약함!
⇒ 브라우저의 개발자 도구만 열어도 DOM 이 어떻게 작성되어있는지, 어떤 서버와 통신하는지, 리소스 출처는 어디인지와 같은 각종 정보 및 소스 코드를 아무 제재없이 열람할 수 있음.
•
이와 같은 상황에서 다른 출처의 어플리케이션이 서로 통신하는 것에 대해 아무 제약도 존재하지 않는다면, 악의를 가진 사용자가 XSS 나 CSRF 와 같은 방법으로 사용자의 정보를 탈취하기가 쉬워짐.
XSS(Cross-Site Scripting) 란?
CSRF(Cross-Site Request Forgery) 란?
•
이 정책으로 인해 신경써야 할 것들이 많아 귀찮을 수도 있지만, CORS 라는 방어막이 존재하기 때문에 우리가 가져다 쓰는 리소스들이 안전하다는 최소한의 보장을 받을 수 있는 것!
CORS 동작 방식
1.
기본적으로 웹은 다른 출처의 리소스를 요청할 때 HTTP 프로토콜을 사용하여 요청을 보냄. 이 때 브라우저는 요청 헤더 (Request Header) 의 Origin 필드에 요청을 보내는 출처를 담아 전송함.
2.
서버는 요청에 대한 응답 헤더 (Response Header) 의 Access-Control-Allow-Origin 이라는 값에 "이 리소스를 접근하는 것이 허용된 출처"를 내려줌.
3.
응답을 받은 브라우저는 자신이 보냈던 요청의 Origin 과 서버가 보내준 응답의 Access-Control-Arrow-Origin 을 비교해본 후 이 응답이 유효한 응답인지 아닌지를 결정함.
⇒ 이는 기본적인 동작 흐름으로, 실제 CORS 동작 방식은 한 가지가 아니라 세 가지의 시나리오에 따라 변경됨.
+) CORS 에러는 브라우저에서 발생하는 것!
•
CORS 정책은 서버에 저장되어 있으며, 저장된 CORS 정책을 브라우저에 보내주는 일을 서버가 맡고 있기는 하지만, 그 CORS 정책을 보내달라고 서버에게 요청하는 건 브라우저임.
•
먼저, 브라우저에서 HTTP 요청이 발생했을 때 CORS 검증을 해야하는 상황인지 판단하며, 검증이 필요한 상황에 해당하면 CORS 검증을 서버에 요청함. 그리고 응답 결과에 따라 브라우저는 발생했던 HTTP 요청을 취소시키고 에러를 뱉음. ⇒ 결과적으로 에러를 발생시키는 건 브라우저임!
CORS 시나리오
Preflight Request
•
이 방식은 요청을 한 번에 보내는 것이 아닌, 예비 요청과 본 요청으로 나누어서 서버에 전송하는 방식으로, 가장 많이 사용됨.
•
본 요청을 보내기 전에 보내는 예비 요청을 프리플라이트(Preflight)라고 하며, 이 예비 요청에는 HTTP 메소드 중 OPTIONS 메소드가 사용됨.
•
예비 요청의 역할은 본 요청을 보내기 전에 브라우저 스스로 이 요청을 보내는 것이 안전한지 확인하는 것임.
브라우저는 본 요청을 보내기 전에 예비 요청을 먼저 보내, 요청의 유효성 (CORS 정책 위반 여부) 을 검사함.
1.
클라이언트가 fetch 또는 XMLHttpRequest API 를 사용하여 브라우저에게 리소스를 받아오라는 명령을 내리면, 브라우저는 서버에게 예비 요청을 먼저 보냄.
2.
서버는 예비 요청에 대한 응답으로 현재 자신이 어떤 것들을 허용하고, 어떤 것들을 금지하고 있는지에 대한 정보를 응답 헤더에 담아서 브라우저에게 보내줌.
3.
브라우저는 자신이 보낸 예비 요청과 서버가 응답에 담아준 허용 정책을 비교한 후, 이 요청을 보내는 것이 안전하다고 판단되면 같은 엔드포인트로 다시 본 요청을 보냄.
4.
서버가 본 요청에 대한 응답을 하면 브라우저는 최종적으로 이 응답 데이터를 클라이언트에 넘겨줌.
Simple Request
•
예비 요청을 보내지 않고 바로 서버에게 본 요청을 보내는 방식으로, 서버가 응답으로 헤더에 Access-Control-Allow-Origin 값을 보내주면 그 때 브라우저가 CORS 정책 위반 여부를 검사하는 방식임.
•
Preflight Request 와 전반적인 로직 자체는 같으며, 예비 요청의 존재 유무만 다름.
•
이는 아무 때나 사용할 수 있는 것이 아닌, 특정 조건을 만족하는 경우에만 예비 요청을 생략할 수 있음. 일반적인 방법으로 웹 어플리케이션 아키텍처를 설계하게 되면 거의 충족하기 어려운 조건이기 때문에 잘 사용하지 않음.
사용 조건 내용
Credentialed Request
•
인증된 요청을 사용하는 방식으로, 이 시나리오는 CORS 의 기본적인 방식이라기보다는 다른 출처 간 통신에서 좀 더 보안을 강화하고 싶을 때 사용하는 방법임.
•
기본적으로 브라우저가 제공하는 비동기 리소스 요청 API 인 fetch 와 XMLHttpRequest 는 별도의 옵션 없이 브라우저의 쿠키 정보나 인증과 관련된 헤더를 함부로 요청에 담지 않음. 이 때 인증과 관련된 정보를 담을 수 있게 해주는 옵션이 credentials 옵션임.
•
이 옵션은 아래와 같은 3가지 값을 사용할 수 있음. 이 옵션들이 포함된다면, 브라우저는 다른 출처의 리소스를 요청할 때 단순히 Access-Control-Allow-Origin 만 확인하는 것이 아니라, 좀 더 빡빡한 검사 조건을 확인하게 됨.
◦
same-origin : 기본값으로, 같은 출처 간 요청에만 인증 정보를 담을 수 있다는 의미
◦
include : 모든 요청에 인증 정보를 담을 수 있다는 의미
◦
omit : 모든 요청에 인증 정보를 담지 않는다는 의미