티스토리 뷰
CORS
Cross-Origin Resource Sharing : 교차 출처 자원 공유
1️⃣ 출처(Origin)란?
웹에서 출처(origin)란 scheme(protocol), hostname(domain), 액세스에 사용되는 URL의 port로 정의된다.
- 스키마, 호스트 이름, 포트가 모두 일치하는 경우에만 동일한 출처를 가진다고 한다.
- 일부 작업은 동일 출처 콘텐츠로 제한되며 이 제한은 CORS를 사용하여 해제할 수 있다 .
일반적인 URL 예시를 살펴보자.
예시 URL의 구성요소는 아래 그림과 같다.
위 URL의 구조에서 Protocol, Host, Port가 같다면 같은 출처라고 취급한다.
자바스크립트로 location객체의 origin 속성을 찍어보면 출처를 확인할 수 있다.
console.log(location.origin);
2️⃣ SOP (Same-Origin Policy) : 동일 출처 정책
한 출처(origin) 에서 로드한 문서 또는 스크립트가 다른 출처의 리소스와 상호 작용할 수 있는 방법을 제한하는 중요한 보안 메커니즘이다.
→ 쉽게 말하면, 같은 출처인 경우에만 리소스를 받을 수 있도록 제한하는 정책이다.
이는 악의적인 웹사이트가 브라우저에서 JS를 실행하여 다른 사이트를 공격하는 등의 상황을 방지한다.
💡 CORS와 SOP가 나오게된 이유
웹의 보안취약점을 공격할 수 있는 CSRF, XSS 등의 방법을 막기위해 CORS와 SOP가 모든 http request에 적용되었다고 한다.
3️⃣ CORS 시나리오 3가지
request 요청에 Origin이라는 헤더가 있고, response에 Access-Control-Allow-Origin이라는 헤더가 있다.
이 두개의 헤더가 같으면 브라우저는 같은 출처라고 인식한다.
CORS가 동작하는 방식은 한 가지가 아니라 세 가지의 시나리오(Preflight Request, Simple Request, Credentialed Request))에 따른다.
▶ Preflight Request (예비 요청)
: 본격적인 교차 출처 HTTP 요청 전에 서버 측에서 그 요청의 메서드와 헤더에 대해 인식하고 있는지를 체크한다.
자바스크립트 fetch()를 사용하여 리소스를 요청하면 OPTIONS라는 메서드를 통해 예비 요청을 보내는데
이 때, 헤더에 요청에 대한 출처를 함께 실어 보낸다.
예비 요청에는 Origin에 대한 정보 뿐만 아니라 Access-Control-Request-Headers, Access-Control-Request-Methode 등 예비 요청 이후에 보낼 본 요청에 대한 다른 정보들도 포함되어 있다.
서버는 Access-Control-Allow-Origin를 응답해주는데 브라우저에서 위 OPTIONS 메서드를 통해 보낸 출처와 Access-Control-Allow-Origin이 같다면 같은 출처라고 인식한다. 따라서, CORS 정책에 위반되지 않았다고 판단하여 본 요청을 보내 요청을 처리한다.
만약, 요청의 Origin과 응답의 Access-Control-Allow-Origin이 같지 않다면 브라우저는 CORS 정책을 위반했다는 에러를 발생시킨다. CORS 에러는 브라우저가 판단하는 것이라 서버에서는 정상적으로 200 응답을 보낸다.
▶ Simple Request
: 예비 요청을 보내지 않고 바로 본 요청을 하며 본 요청이 위의 예비 요청에서 한 행동을 모두 포함하고 있다. 위의 시나리오와 마찬가지로, Origin과 Access-Control-Allow-Origin을 비교해서 CORS를 위반했는지 판단한다.
Simple Request는 아래의 특정 조건을 만족하는 경우에만 사용할 수 있는데 이 조건들이 까다로워서 실질적으로 보기 힘든 경우라고 한다.
- 요청 메서드는 GET, HEAD, POST 중 하나여야 한다.
- 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만 허용된다.
JWT를 사용하는 경우 위의 두 번째 조건을 만족하지 못하고,
rest API를 사용하는 경우 application/json이 허용되지 않는 세 번째 조건을 만족하지 못한다.
따라서, Simple Request를 실질적으로 볼 일은 거의 없다.
▶ Credentialed Request
: 인증된 요청을 사용하는 방법으로, CORS의 기본적인 방식이라기 보단 다른 출처 간의 통신에서 보안을 조금 더 강화하는 방법이다.
fetch 등의 비동기 API는 기본적으로 쿠키를 담아서 요청을 보내지 않는다. 쿠키를 담으려면 아래와 같은 추가적인 옵션 값이 주어 서버에서 이 요청을 보낸 사람이 인증이 된 사용자인지 한번 더 검증을 한다.
옵션 값 | 설명 |
same-origin(기본값) | 같은 출처 간의 요청에만 인증 정보를 담는다. |
Include | 모든 요청에 인증 정보를 담는다. |
Omit | 모든 요청에 인증 정보를 담지 않는다. |
fetch안에 credentials라는 옵션을 주어 쿠키를 함께 담아 보내고, 서버에서는 인증이 완료되면 정상적인 응답을 보낸다. 마찬가지로 브라우저에서 CORS 정책을 위반하는지 판단한다.
Credentialed Request도 제약사항이 존재한다.
- Access-Control-Allow-Origin에 *을 사용할 수 없으며, 명시적인 URL이어야 한다.
- 응답 헤더에는 반드시 Access-Control-Allow-Credentials: true가 존재해야 한다.
4️⃣ 결론
CORS란 모든 출처의 자원을 받는 것은 보안 상의 위험이 있으니 원하는 출처의 자원만 받을 수 있도록 제한을 할 수 있는 정책이다.
Server Side에서는 응답 헤더에 올바른 Access-Control-Allow-Origin을 세팅해주어야 한다.
Client Side에서는 Webpack Dev Server로 리버스 프록싱하여 우회가 가능하다. 그러나 이러한 방법은 로컬환경에서만 가능하니 가장 좋은 방법은 서버 개발자에게 도움을 요청해야 한다.
참고 자료
'🙋♀️ 기술면접 대비' 카테고리의 다른 글
Currying과 Composition (0) | 2023.02.15 |
---|---|
CSR, SSR, SSG, ISR (0) | 2022.09.27 |
브라우저 렌더링 과정 (0) | 2022.09.26 |
프론트엔드 개발자 삐롱히의 개발 & 공부 기록 블로그