본문 바로가기
개발자 준비/RestAPI

[RestAPI] CORS (Cross Origin Resource Sharing)

by osul_world 2022. 1. 19.
728x90

 

첫 프로젝트를 진행하다가 어느순간 마주친 이 CORS라는 녀석을 정리해보고자 한다. 웹 개발을 했다면 마주치게 된다는 유명한 이슈였다. (드디어 나도 이걸 보는구나..!!)

 

 

CORS

Cross-Origin Resource Sharing 로 교차 출처 리소스 공유라는 뜻이다.

한국말로 의미를 풀어보자면 다른 출처로부터 오는 리소스를 허용한다는 의미이다.

 

 

출처란?

알고있다 싶이 URI는 다양한 구성 요소로 리소스이 위치를 표시하고있다.

 

img

 

웹에서 출처란 Protocol + Host + Port 를 의미한다.

이 3가지 요소중에 하나라도 다르면 다른 출처로 간주한다는 것이다.

반대로 이 3가지 요소만 같으면 동일 출처로 간주한다는 것이다.

 

 

SOP(Same-Origin Policy: 동일출처정책)

웹 생태계에서는 다른 출처로부터 오는 리소스를 제한하는 정책이 존재한다.

바로 SOP일한 출처만 API나 데이터 접근이 가능하도록 한다는 동일출처정책이다.

 

 

왜 다른 출처를 차단하는걸까?

출처가 다른 두 개의 어플리케이션이 마음대로 소통할 수 있는 환경은 꽤 위험한 환경이다.

사용자가 페이스북에 접근한다고 가정 해보자.

 

 

브라우저에는 사용자에 대한 정보가 저장되어있다.

세션,쿠키,로그인 정보 등

 

 

사용자는 페이스북에 방문할때 이 정보를 함께 가지고 방문하는데

어떤 악의적인 사이트 X가 사용자에게 접속을 유도해 사용자의 정보와 함께 페이스북으로 요청을 보내 사용자인것 처럼 위장해 페이스북에게 인증을 받고 여러 정보를 접근할 수 있다.

메일 이나 스팸 등으로 사용자가 접속하게 함

 

 

 

img

 

 

이를 방지하기 위해 등장한게 SOP이다.

 

여기서 Origin 은 요청의 출처를 뜻한다.

facebook이라는 출처가 아닌 다른 출처로부터 요청이 오면 해당 요청을 차단함으로 써 위와 같은 문제를 막는 것이다.

 

 

CORS!

이쯤되면 CORS가 뭔지 감이 잡혔을 것이다.

SOP는 다른 출처로부터 오는 요청을 막아내는 역할을 하는것이고 이 요청을 받고싶으면 CORS 를 설정해

해당 출처를 허용하면 받을수있도록 한다는 것이다.

 

CORS의 동작방식

출처를 비교하는 로직은 서버에 존재하는게 아닌 브라우저에 포함되는 로직으로 CORS는 서버 간 통신을 할 때는 이 정책이 적용되지 않는다.

브라우저에 포함되는 스팩임을 알아두자

 

 

기본흐름

우리가 CORS 정책을 위반하는 리소스 요청을 하더라도 해당 서버가 같은 출처에서 보낸 요청만 받겠다는 로직을 가지고 있는 경우가 아니라면 서버는 정상적으로 응답을 하고,

 

이후 브라우저가 이 응답을 분석해서 CORS 정책 위반이라고 판단되면 그 응답을 사용하지 않는 것이다.

 

img

 

 

웹 클라이언트 어플리케이션이 다른 출처의 리소스를 요청할 때는 HTTP 프로토콜을 사용하여 요청을 보내게 되는데, 이때 브라우저는 요청 헤더Origin이라는 필드에 요청을 보내는 출처를 함께 담아보낸다.

 

Origin: https://evan-moon.github.io

이후 서버가 이 요청에 대한 응답을 할 때 응답 헤더의 Access-Control-Allow-Origin이라는 값에 “이 리소스를 접근하는 것이 허용된 출처”를 내려주고,

 

이후 응답을 받은 브라우저는 자신이 보냈던 요청의 Origin과 서버가 보내준 응답의 Access-Control-Allow-Origin을 비교해본 후 이상이 없으면 해당 리소스를 내려받는다.

 

서로 다르면 CORS 정책에 의해 origin이 다르니 차단. CORS 터트림!

 

 

기본 흐름의 맥락은 요청시 Origin에 담긴 출처와 서버가 응답한 Access-Control-Allow-Origin의 출처 목록들을 비교해서 요청을 차단할지 말지 브라우저가 결정한다는 것이다.

서버 응답 헤더에 Access-Control-Allow-Origin 에 Origin이 존재하면 리소스를 내려 받는다. 없다면 CORS 정책 위반 ERROR 발생시킴

 

 

CORS의 접근제어 시나리오

기본 맥락은 위처럼 간단하지만 CORS는 3가지 시나리오를 가지고있다.

 

Simple Requset : 단순요청

Preflight Request : 사전요청

Credintialed Request : 인증정보 포함 요청

 

 

Preflight Request : 사전요청

프리플라이트(Preflight) 방식은 일반적으로 우리가 웹 어플리케이션을 개발할 때 가장 마주치는 시나리오이다.

 

이 시나리오에 해당하는 상황일 때 브라우저는 요청을 한번에 보내지 않고 예비 요청과 본 요청으로 나누어서 서버로 전송한다.예비 요청을 보내보고 본 요청을 보낼지 판단한다.

 

 

예비요청과 본요청 2번이 왔다갔다하는것은 비효율적이기 때문에 서버는 예비요청이후 응답 캐시를 설정하고 본 요청에 대한 응답을 처리한다.

 

 

본 요청을 보내기 전 브라우저 스스로 이 요청을 보내는 것이 안전한지 확인하기위해 서버에게 예비 요청을 보내는것Preflight Request라고 한다.

 

그림으로 보면 이런 형식이다.

 

img

 

Preflight Request HTTP methodOPTIONS라는 메서드를 이용해 요청을 보내며 이때 헤더에 아래와 같은 정보를 꼭 포함해야한다.

 

Origin: 요청의 출처

Access-Control-Request-Headers: 본 요청에 사용될 HTTP method

Access-Control-Request-Method: 본 요청에 추가 헤더

 

 

이 정보를 가지고 서버에게 예비 요청을 보내면 서버는 이 예비 요청에 대한 응답을 보내준다.

응답 헤더에 Access-Control-Allow-Origin 에 접근이 가능한 출처 목록을 담아 응답한다.

 

 

브라우저는 기본흐름 과 같이 Origin 과 Access-Control-Allow-Origin을 비교해서 CORS 정책 위반 에러 발생 여부를 결정한다.

 

 

Simple Requset : 단순요청

단순 요청은 예비 요청을 보내지 않고 바로 서버에게 본 요청부터 때려박은 후,

 

서버가 이에 대한 응답의 헤더에 Access-Control-Allow-Origin과 같은 값을 보내주면 그때 브라우저가 CORS 정책 위반 여부를 검사하는 방식이다.

 

img

 

 

예비 요청을 생략하는건 아래 조건이 모두 만족하는 특수한 상황에만 생략이 가능하다.

 

  1. 요청의 메소드는 GET, HEAD, POST 중 하나여야 한다.
  2. Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width를 제외한 헤더를 사용하면 안된다.
  3. 만약 Content-Type를 사용하는 경우에는 application/x-www-form-urlencoded, multipart/form-data, text/plain만 허용된다.

 

 

2,3번 조건이 까다롭다.

사용자 인증에 사용되는 Authorization 헤더 조차 저 조건에는 포함되지 않는다.

 

또한 대부분의 HTTP API는 text/xml이나 application/json 컨텐츠 타입을 가지도록 설계되기 때문에 사실 상 이 조건들을 모두 만족시키는 상황을 만들기는 그렇게 쉽지 않은 것이 현실이다.

 

사실상 거의 사용되어질 일이 없다.

 

 

Preflight Request 즉, 예비 요청이 왜 필요할까?

 

이쯤에서 의문을 가져야한다.

 

다 Simple Request하면 안되나?

 

CORS는 서버를 위한 정책이다.

 

img

 

CORS에 대한 처리가 안되어있는 서버가 있다.

 

클라이언트가 Preflight Request 없이 Simple Request로 PUT,DELET,POST등의 서버 리소스에 영향을 주는 요청을 보낸다면 서버는 요청에 대한 처리를 할것이고 리소스를 삭제하던 생성하던 서버 DB를 수정할것이다. 큰일이 날수도있다

 

CORS 위반은 응답처리 이후에 판별할수있기 때문에 서버는 응답처리를 끝마친 후 일것이다.

 

 

그리고 당연하게도 이 서버는 CORS 처리가 안되어있기 때문에 Access-Control-Allow-Origin가 없을것이고

클라이언트는 CORS 에러를 발생시킬것이지만 이미 서버는 요청에대한 수행을 한 뒤이다.

 

그럼 클라이언트가 Preflight Request 를 한다면?

 

img

 

서버에 영향을 끼치지 않는 Preflight Request 로 서버가 CORS 처리가 되어있는지 확인하고 안되어있다면

추가로 요청을 보내지 않는다.

 

이렇게 함으로써 CORS처리가 안되어있는 서버를 지킬수있다.

 

하지만 CORS에 무조건 의존하는것은 옳지않다.

 

악의적인 요청으로 서버에게 요청에 대한 처리를 강행시킬수있기 때문에 개발자는 CORS 처리와 더불어 여러 방법으로 이를 방지해야한다.

 

 

Credintialed Request : 인증정보 포함 요청

 

3번째 시나리오는 인증된 요청을 사용하는 방법이다.

 

이 시나리오는 CORS의 기본적인 방식이라기 보다는 다른 출처 간 통신에서 좀 더 보안을 강화하고 싶을 때 사용하는 방법으로, 인증관련 헤더를 포함할때 사용하는 요청이다.

 

브라우저는 쿠키 정보나 인증과 관련된 헤더를 함부로 요청에 담지 않는다.

 

이때 인증 관련 정보를 헤더에 담게 해주는 옵션이 credentials 옵션이다.

옵션 값 설명
same-origin (기본값) 같은 출처 간 요청에만 인증 정보를 담을 수 있다
include 모든 요청에 인증 정보를 담을 수 있다
omit 모든 요청에 인증 정보를 담지 않는다

 

클라이언트

credentials : include

 

서버

Access-Control-Allow-Credentials : true

 

include와 같은 옵션을 사용하여 리소스 요청에 인증 정보가 포함된다면, 이제 브라우저는 다른 출처의 리소스를 요청할 때 단순히 Access-Control-Allow-Origin만 확인하는 것이 아니라 좀 더 빡빡한 검사 조건을 추가하게 된다.

 

서버는 응답 헤더의 Access-Control-Allow-Origin을 * 로 설정하면 안된다.

* 은 와일카드라는 의미로 모든 Origin을 허용한다는 뜻

정확한 Origin을 표기해 줘야한다.

 

즉, 정리하자면

  1. Access-Control-Allow-Origin에는 *를 사용할 수 없으며, 명시적인 URL이어야한다.
  2. 응답 헤더에는 반드시 Access-Control-Allow-Credentials: true가 존재해야한다.

 

 

CORS를 해결할 수 있는 방법

 

직접 헤더의 Access-Control-Allow-Origin 세팅하기

CORS 정책 위반으로 인한 문제를 해결하는 가장 대표적인 방법은,

그냥 정석대로 서버에서 Access-Control-Allow-Origin 헤더에 알맞은 값을 세팅해주는 것이다.

이때 와일드카드인 *을 사용하여 이 헤더를 세팅하게 되면 모든 출처에서 오는 요청을 받아먹겠다는 의미이므로 당장은 편할 수 있겠지만, 바꿔서 생각하면 정체도 모르는 이상한 출처에서 오는 요청까지 모두 받아먹겠다는 오픈 마인드와 다를 것 없으므로 보안적으로 심각한 이슈가 발생할 수도 있다.

그러니 가급적이면 귀찮더라도 Access-Control-Allow-Origin: https://evan.github.io와 같이 출처를 명시해주도록 하자.

 

프론트 프록시 서버 설정

앞서 CORS는 브라우저에 포함된 스펙이기 때문에 서버끼리 통신에는 작용하지 않는다고 설명했다.

이를 이용해 프론트 서버를 만들고 브라우저는 프론트서버와 통신하도록 한다.

브라우저 입장에서는 프론트 서버와만 통신하기때문에 동일 출처로 판단하기때문에 CORS가 발생하지 않는다.

하지만 프론트 서버는 내부적으로 백엔드 서버와 통신하며 API가 필요한경우 요청해서 받아오게된다.

즉, 프론트 서버와 백엔드 서버를 분리해서 브라우저의 처리를 모두 프론트서버에게 위임하고 프론트서버는 필요시 백엔드 서버에게 리소스를 요청해 받아오는식으로 설계하면 CORS 발생을 방지할수있다.

 

img

 

 

 

Reference


https://evan-moon.github.io/2020/05/21/about-cors/

https://www.youtube.com/watch?v=-2TgkKYmJt4

 

 

728x90