[이론편] SOP 와 CORS

7927 단어 corswebcors

웹 개발을 하면서 CORS 는 반드시 한번쯤 겪게 되는 문제다

하지만, 보통은 구글신께 도움을 구해서 해결만 하고, URL 이 달라서 발생하는 문제다 라고 까지만 인지하고 있는 경우가 많다. 그래서 오늘은 CORS 의 탄생 배경 부터 해결 방법까지 정리해보려고 한다.

CORS 에 대해서 자세하게 알아 보기 전에 CORS 전에 있던 보안 방식인 SOP 에 대해서 알아보자

SOP 란 ?

SOP 는 Same Origin Policy 의 줄임말로 "같은 출처에서만 리소스를 공유할 수 있다" 라는 규칙을 가진 정책 이다.

출처(origin) 이란 ?

github URL 이 있다고 했을 때 Protocol, Host, Port 를 모두 합친 걸 출처(origin) 이라고 한다
즉, 출처는 서버의 위치를 찾아가기 위해 가장 기본적인 것들이다.

예를 들어서 http://localhost 가 있을 때

URL같은 출처 여부이유
http://127.0.0.1X(O) Port 80 으로 동일
(O) Protocol http:// 로 동일
(X) Host 127.0.0.1 로 다름
http://localhost/api/corsO(O) Port 80 으로 동일
(O) Protocol http:// 로 동일
(O) Host localhost 로 동일
https://localhostX(O) Port 80 으로 동일
(X) Protocol https:// 로 다름
(O) Host localhost 로 동일
http://localhost:80?(?) Port 브라우저 구현에 따라 다름
(O) Protocol http:// 로 동일
(O) Host localhost 로 동일

마지막의 경우 출처에 http://localhost:80 처럼 포트 번호가 명시 되어 있지 않기 때문에 판단하기가 애매하다.

이건 브라우저에 따라 같은 출처 여부인지 판단하는게 달라지며, Internet Explorer 같은 경우는 출처 비교 시 포트를 아예 무시한다.

출처 비교 로직 위치

출처를 비교하는 로직은 서버가 아니라 브라우저에 구현되어 있다.

만약, CORS 정책을 위반하는 리소스 요청을 한 경우
서버는 정상적으로 응답을 하고, 이후 브라우저가 응답을 분석해서 정책 위반이라고 판단하는 경우 응답을 사용하지 않고 버린다.

​ ➡️ 그렇기 때문에 서버에서는 작업이 수행이 되었는데 브라우저에서는 정상적으로 응답하지 않는 경우가 발생될 수 있다.

SOP 를 사용해야하는 이유

그렇다면 왜 오류만 많이 발생시키는 SOP 정책을 반드시 지켜야 할까 ??
아래 예제를 통해 알아보자

  1. [👩 사용자] 페이스북 서비스 (https://facebook.com) 에 로그인 이후 사용
    (페이스북으로 부터 인증 토큰을 받아와서 브라우저에서 가지고 있음)

  2. [👩‍💻 해커] 흥미진진한 내용과 함께 링크를 사용자에게 메일로 전송

  3. [👩 사용자] 메일을 클릭하기 https://hacker.ck 로 이동 (해당 주소에 해커가 인증 토큰을 가져가기 위한 스크립트를 미리 작성해둠)

  4. [👩‍💻 해커] 인증 토큰을 가지고 페이스북에 포스트를 게시

과 같은 상황이 있다고 했을 때 SOP 는 4번에서 위력을 발휘하는데

해커의 origin 은 https://hacker.ck 이고 페이스북의 origin 은 https://facebook.com 으로 origin 이 상이하기 때문에 오류가 발생하기 때문에 포스트를 게시하지 못한다.

만일 SOP 정책이 적용되지 않는다고 하면 해커는 인증 토큰을 가지고 내가 원하지 않는 포스트를 게시했을 거다.

하지만 다른 출처의 리소스를 필요로 하는 경우에는 어떻게 해야 할까 ?
➡️ 그럴 때에는 CORS 를 적용해야 한다.

CORS 란 ?

CORS 는 Cross Origin Resource Sharing 의 줄임말로 한국어로 번역했을 때 다른 출처의 자원을 공유 하는 것이다.

🗣 MOZILLA 에선

CORS 는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른출처의 선택한 자원에 접근 할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제

라고 정의한다.

🌟 보통 우리가 CORS 오류라고 많이 말하는데 정확하게 얘기하자면 CORS 는 정책이기 때문에 CORS (정책 위반) 오류 라고 생각하는게 제일 정확하다. 필자는 CORS 자체가 오류라고 생각해서 CORS 를 이해하는데 시간이 오래 걸렸다,,

CORS 동작 방식

CORS 의 동작 방식은 다음과 같다.

  1. 다른 출처의 리소스를 요청할 때 브라우저는 요청 헤더에 Origin이라는 필드에 요청을 보내는 출처를 함께 담아보낸다.

  2. 서버가 이 요청에 대한 응답을 할 때 응답 헤더의 Access-Control-Allow-Origin이라는 값에 “이 리소스를 접근하는 것이 허용된 출처”를 내려주고, 이후 응답을 받은 브라우저는 자신이 보냈던 요청의 Origin과 서버가 보내준 응답의 Access-Control-Allow-Origin을 비교해본 후 이 응답이 유효한 응답인지 아닌지를 결정한다.

만약 일치 하지 않는 경우에는 오류를 발생시킨다.

CORS 접근 시나리오

CORS 가 동작하는 방식은 세가지 시나리오가 존재한다.

1. 프리플라이트 요청 (Preflight Request)

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

  • 본 요청을 보내기 전에 예비 요청을 통해 CORS 가 허용 되어 있는지 사전 확인 한다
  • 프리플라이트 요청을 보내고 난 다음에 Response 로 온 응답 캐시 기간 동안 응답 response 들을 캐싱해두고 사용한다.
  • 예비 요청 HTTP 메서드는 OPTIONS 가 사용된다.

프리 플라이트 요청이 필요한 이유

  1. CORS 를 모르는 서버를 위해서
    CORS 판단은 서버가 아니라 브라우저에서 이루어지기 때문에 API 를 찌를때 이미 작업이 완료 되었는데 브라우저에서는 CORS 에러를 뱉을 수 있다.

  2. CORS 가 생기기 전 서버를 위해서
    CORS 가 생기기 이전에 만들어진 서버들을 브라우저의 SOP request 만 가능하다는 가정에서 만들어졌는데, cross-site request가 CORS로 인해서 가능해졌기 떄문에 이런 서버들은 cross-site request에 대한 security mechanism이 없다보니 보안적으로 문제가 생길수 있으니 이런 서버들을 보호하기 위해 CORS spec에 preflight request를 포함한다.

프리 플라이트 요청의 과정
1. OPTIONS 메서드를 통해 다른 도메인의 리소스 요청이 가능한지 확인하고
2. 요청이 가능하다면 실제 요청을 보낸다.

Preflight Request

이름설명
origin요청 출처
Access-Control-Request-Method실제 요청의 메서드
Access-Control-Request-Headers실제 요청의 추가 헤더

Preflight Response

이름설명
Access-Control-Allow-Origin서버 측 허가 출처
Access-Control-Allow-Methods서버 측 허가 메서드
Access-Control-Allow-Headers서버 측 허가 헤더
Access-Control-Max-AgePreflight 응답 캐시 기간

2. 단순 요청 (Simple Request)

  • 바로 요청을 날리고 서버가 Access-Control-Allow-Origin 값을 보내주면 그때 브라우저가 CORS 정책 위반 여부를 검사한다.

하지만, 매번 사용할 수 있는 건 아니고 특정 조건을 만족해야 예비 요청을 사용하지 않을 수 있다. 조건은 다음과 같다

  1. 요청 메서드는 GET, POST, HEAD 중 하나
  2. Content-Type 은 Application/x-www-form-urlencoded, Multipart/form-data, text/plain 만 허용
  3. 헤더는 Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width만 허용

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

  • 인증 관련 헤더를 포함할 때 사용하는 요청이다.

클라이언트 credentials 옵션에 총 3가지 값을 설정할 수 있다

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

credentials 옵션을 사용해서 리소스를 요청하는 경우 브라우저는 Access-Control-Allow-Origin 만 확인하는게 아니라 빡빡한 검사 조건을 적용시킨다.

요청에 인증 정보가 담겨있는 상태에서 다른 출처의 리소스를 요청하는 경우 브라우저는 CORS 정책 위반 여부를 판단하기 위해서 두가지 규칙이 추가적으로 본다.

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

CORS 해결하기

CORS 는 서버에서 해결하는 방법과 프론트에서 해결하는 방법 총 두가지가 있는데 자세한 해결 방법은
➡️ [실습편] CORS 적용하기
를 참고해주세요 !

🙇🏻‍♀️ 레퍼런스

[10분 테코톡] 🌳 나봄의 CORS

CORS는 왜 이렇게 우리를 힘들게 하는걸까?

좋은 웹페이지 즐겨찾기