[웹 서버 ]기초

hyena_lee·2023년 2월 6일
0

Node.js

목록 보기
1/3
post-thumbnail

🌖 CORS(Cross Origin Resource Sharing

1. Origin의 의미

Origin은 URL에서 프로토콜, 도메인, 포트 번호를 합친 부분을 의미
URL : https://it-eldorado.com:80/posts/123456?data=789#abc
(1) 프로토콜(Scheme이라고도 함)에 해당하는 부분은 https://
(2) 도메인에 해당하는 부분은 it-eldorado.com
(3) 포트 번호에 해당하는 부분은 :80이다.
따라서 Origin은 https://it-eldorado.com:80이다.
참고로, window.location.origin 명령어를 실행하면 현재 접속 중인 웹사이트의 Origin을 알아낼 수 있다.

Origin : https://it-eldorado.com:80

2. SOP와 CORS의 관계

브라우저의 보안 관련 정책인 SOP(Same Origin Policy)와 CORS(Cross Origin Resource Sharing)의 관계를 알아보자. SOP는 한국말로 '동일 출처 정책'이라고 부르며, CORS는 '교차 출처 자원 공유'라고 부른다. 갑자기 SOP를 소개하는 이유는, SOP가 CORS와 아주 긴밀한 관계를 가지고 있기 때문이다. 둘 간의 관계에 주목하여 설명을 따라가 보자.

SOP(Same Origin Policy)는 다른 Origin으로 요청을 보낼 수 없도록 금지하는 브라우저의 기본적인 보안 정책이다. 즉, 동일한 Origin으로만 요청을 보낼 수 있게 하는 것이다. 실제로 아주 옛날에는 이것이 절대적인 규칙이었기 때문에, 다른 Origin으로 요청을 보내는 건 애초에 불가능하였다. 그러나 기술이 발달하면서 서로 다른 Origin끼리 데이터를 주고받아야 하는 일이 많아졌고, 이로 인해 SOP는 별도의 예외 사항을 두게 되었다. 즉, 몇 가지 예외 상황에 대해서는 다른 Origin으로도 요청을 보낼 수 있게 하는 것이다.

즉, CORS(Cross Origin Resource Sharing)는 다른 Origin으로 요청을 보내기 위해 지켜야 하는 정책으로, 원래대로라면 SOP에 의해 막히게 될 요청을 풀어주는 정책이라고 볼 수 있다. 하도 우리를 짜증 나게 해서 요청을 막는 정책인 줄 알았더니, 오히려 요청을 보낼 수 있도록 돕는 정책이었던 것이다.

여기서 주목할 만한 점은, CORS는 '브라우저'의 정책이라는 것이다. 즉, 서버는 평소처럼 요청이 오면 응답을 해줄 뿐이고, 브라우저가 자신이 보낸 요청 및 서버로부터 받은 응답의 데이터가 CORS 정책을 지키는지 검사하여 안전한 요청을 보낸 건지 검사하는 것이다. 따라서 서버가 정상적으로 응답을 해줬더라도, 알고 보니 안전한 요청이 아니라고 판단되면 해당 응답을 버린다. 그렇기 때문에 서버 간 통신에서는 이러한 정책이 전혀 적용되지 않는다는 것을 알 수 있다.

3. CORS 동작 원리

CORS의 기본적인 동작 원리는 단순하다. 브라우저는 다른 Origin으로 요청을 보낼 때 Origin 헤더에 자신의 Origin을 설정하고, 서버로부터 응답을 받으면 응답의 Access-Control-Allow-Origin 헤더에 설정된 Origin의 목록에 요청의 Origin 헤더 값이 포함되는지 검사하는 것뿐이다. 즉, CORS 요청을 위해서는 서버에서 응답의 Access-Control-Allow-Origin 헤더에 허용되는 Origin의 목록 혹은 와일드카드(*)를 설정해주면 된다. 이것이 기본적인 CORS 정책이다.

3-1. 단순 요청 (Simple Request)

단순 요청(Simple Request)은 공식 용어가 아닌, MDN에서 사용한 용어를 가져온 것이다. 그런데 이름이 의미하는 것과 달리, 단순 요청은 흔한 유형의 요청이 아니다. 단순 요청이 되기 위한 조건이 매우 까다롭기 때문이다. 그 조건은 대략 다음과 같다.

위와 같은 조건을 만족하는 단순 요청은 안전한 요청으로 취급되어, (뒤에서 설명할) 프리플라이트 요청이 필요 없이 단 한 번의 요청만을 전송한다. 즉, 위에서 언급한 CORS의 기본적인 동작 원리를 그대로 따른다. 다른 Origin으로 요청을 보낼 때 Origin 헤더에 자신의 Origin을 설정하고, 서버로부터 응답을 받으면 응답의 Access-Control-Allow-Origin 헤더에 설정된 Origin의 목록에 요청의 Origin 헤더 값이 포함되는지 검사하는 것이다. 이를 그림으로 나타내면 다음과 같다.

3-2. 프리플라이트 요청 (Preflight Request)

단순 요청의 조건에 벗어나는(= 안전하지 않은) 요청의 경우, 서버에 실제 요청을 보내기 전에 예비 요청에 해당하는 프리플라이트 요청(Preflight Request)을 먼저 보내서 실제 요청이 전송하기에 안전한지 확인한다. 만약 안전한 요청이라고 확인이 된다면, 그때서야 실제 요청을 서버에게 보낸다. 따라서 총 두 번의 요청을 전송한다.

프리플라이트 요청의 특징은 다음과 같다.

서버는 이러한 프리플라이트 요청에 대해 다음과 같은 특징을 가진 응답을 제공해야 한다.


이러한 응답을 받고 나면 브라우저는 이 응답의 정보를 자신이 전송한 요청의 정보와 비교하여 실제 요청의 안전성을 검사한다. 만약 이 안전성 검사에 통과하게 된다면, 그때서야 실제 요청을 서버에게 보낸다. 단, 이때는 Access-Control-Request-XXX 형태의 헤더는 보내지 않는다.

예를 들어, Content-Type 헤더의 값이 application/json이고 사용자 정의 헤더로 Custom-Header를 사용하는 POST 요청을 서버에게 보내려 한다고 해보자. 그러면 이는 단순 요청의 조건에 벗어나기 때문에 프리플라이트 요청이 필요하다. 따라서 총 두 번의 요청이 서버에게 전송되며, 이를 그림으로 나타내면 다음과 같다.

3-3. 인증 정보를 포함한 요청 (Credentialed Request)

두 유형의 요청은 인증 정보가 없는 경우였다. 여기서 말하는 인증 정보(Credential)란 쿠키(Cookie) 혹은 Authorization 헤더에 설정하는 토큰 값 등을 일컫는다. 만약 이러한 인증 정보를 함께 보내야 하는 요청(Credentialed Request)이라면, 별도로 따라줘야 하는 CORS 정책이 존재한다. 이에 대해 한 번 알아보자.

우선, 쿠키 등의 인증 정보를 보내기 위해서는 클라이언트 단에서 요청 시 별도의 설정이 필요하다. 이는 Ajax 요청을 위해 어떠한 도구를 사용하느냐에 따라 달라진다. 만약 XMLHttpRequest, jQuery의 ajax, 또는 axios를 사용한다면 withCredentials 옵션을 true로 설정해줘야 한다. 반면, fetch API를 사용한다면 credentials 옵션을 include로 설정해줘야 한다. 이러한 별도의 설정을 해주지 않으면 쿠키 등의 인증 정보는 절대로 자동으로 서버에게 전송되지 않는다.

위와 같은 설정을 통해 인증 정보를 요청에 포함시켰다면, 이 요청은 이제 인증 정보를 포함한 요청이 된다. 그리고 서버는 이러한 요청에 대해 일반적인 CORS 요청과는 다르게 대응해줘야 한다. 응답의 Access-Control-Allow-Origin 헤더가 와일드카드(*)가 아닌 분명한 Origin으로 설정되어야 하고, Access-Control-Allow-Credentials 헤더는 true로 설정되어야 한다. 그렇지 않으면 브라우저에 의해 응답이 거부된다. 이를 그림으로 나타내면 다음과 같다.

🌖 * Buffer & Stream

fs.readFile에서 data.toString()을 거치지 않고 data만 출력하면 <Buffer 72 65 61 64 20 6d 65 20 62 72 6f 21>와 같이 Buffer가 출력된다.

데이터를 조각(청크, chunk)내어 buffer에 채운 후 다 차면 buffer를 통째로 옮기고 새 buffer에 아직 옮기지 못한 데이터 조각을 다시 채운다. 데이터 조각을 buffer에 채우는 일을 버퍼링(buffering)이라고 부른다. 영상이 버퍼링 중이라며 재생되지 않는 경우를 종종 경험했을텐데 buffer에 데이터를 채울 때까지 기다리는 버퍼링 작업을 말하는 것이다.

한편 buffer가 다 차면 이를 전송하고 다시 buffer를 채우는 버퍼링 작업을 연속하는 것이 스트림(stream)이다. 단발성 single buffer도 존재하지만 지속적으로 buffer가 나오는 것을 stream buffer라고 한다. 버퍼를 이용해 데이터를 전송하는 '흐름'이 스트림이라고 이해하자.

profile
실수를 두려워 말고 계속 도전 하는 개발자의 여정!

0개의 댓글