[HTTP/Server] SOP와 CORS (1) - CORS의 동작 방식과 흐름 이해하기

초코침·2023년 4월 6일
0

HTTP/Server

목록 보기
3/6

SOP

동일 출처 정책(SOP, Same Origin Policy)은 어떤 출처에서 불러온 문서/스크립트가 다른 출처에서 가져온 리소스와 상호작용하는 것을 제한하는 보안 방식이다.

즉, 동일한 출처에서만 리소스를 공유할 수 있다는 정책이다.

동일한 출처(origin)란?
두 URL의 프로토콜, 포트, 호스트가 모두 같은 경우 출처가 동일하다고 할 수 있다.

SOP의 존재 이유
이러한 제약이 없다면, CSRFXSS 등의 공격으로 의도하지 않았던 악성 출처에 아무런 제한 없이 접근할 수 있게 된다.
이러한 경우를 방지하기 위해 SOP 정책으로 동일하지 않은 출처에 접근할 수 없도록 하는 것이다.


이렇게 SOP에 의해 자신의 출처(프로토콜, 호스트, 포트)가 다른 출처는 차단된다.

이때, 출처 비교와 차단 로직은 브라우저에 구현되어 있는 스펙이다.

따라서 출처 비교와 차단은 서버가 아닌 브라우저가 수행한다.

CORS

교차 출처 리소스 공유(Cross-Origin Resource Sharing)는 SOP에 대한 일종의 예외 사항으로, 다른 출처의 리소스 공유에 대한 허용 여부를 가르는 정책이다.

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

많은 정보를 담은 하나의 웹 페이지를 만들 때, 모든 내용을 한 출처에서 가져올 순 없을 것이다. 하지만 SOP 정책으로 인해 출처가 같은 곳에서만 리소스를 공유할 수 있는데, 이때 몇 가지 예외를 두어 출처가 다른 곳에서도 리소스를 공유받을 수 있게 해 주는 것이 CORS다.

즉, SOP를 위반하더라도 CORS에 명시되어 있다면 다른 출처와의 리소스 공유를 허용한다는 것이다.

CORS 동작 흐름

클라이언트에서 서버로 HTTP 프로토콜을 이용해 요청을 보낼 때, 요청의 헤더를 살펴보면 Origin이라는 필드가 있는데 여기에 서버에 요청을 보내려는 출처가 담겨 있다.

다음은 localhost에서 특정 서버로 요청을 보냈을 때의 요청 헤더 부분이다.

클라이언트의 요청을 받은 서버는 응답을 보내게 되는데, 이때 응답 헤더에 서버 출처에 접근하는 것이 허용된 출처를 담아 응답한다. 이는 Access-Control-Allow-Origin 필드에 담겨 있다.

요청을 보내고 응답 헤더 부분을 살펴보면, 서버에서 ‘모든 출처에서 접근 가능하다’*를 보내주었기 때문에 해당 요청은 다른 출처이지만 리소스를 획득할 수 있을 것이다.

브라우저는 요청의 OriginAccess-Control-Allow-Origin을 비교하여 차단 여부를 결정한다. 이때 유효하지 않다면 CORS 에러가 발생하게 된다.

CORS 동작 방식

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

본 요청을 보내기 전에 미리 예비 요청을 보내 안전한 요청인지 확인한 다음 본 요청을 보내는 방식이다.

예비 요청은 OPTIONS 메서드로 보낸다.

브라우저의 preflight 요청은 네트워크 탭에서 확인할 수 있다.


PreFlight의 요청 흐름은 다음과 같다.

  1. 클라이언트에서 서버에 요청을 보낸다.
  2. 브라우저는 클라이언트의 요청을 서버로 보내기 전, OPTIONS 메서드로 preflight 요청을 보낸다.
    • 요청 헤더의 Origin 필드에 요청을 보내려는 출처를 담는다.
    • 요청 헤더의 Access-Control-Request-Method 필드에 사용하려는 메서드를 담는다.
    • 요청 헤더의 Access-Control-Request-Headers 필드에 사용할 헤더들을 담는다.
  3. 서버에서는 preflight 요청에 대한 응답을 보낸다.
    • 응답 헤더의 Access-Control-Allow-Headers 필드에 허용하는 헤더들의 목록을 담는다.
    • 응답 헤더의 Access-Control-Allow-Methods 필드에 허용하는 메서드를 담는다.
    • 응답 헤더의 Access-Control-Allow-Origin 필드에 허용하는 Origin 목록을 담는다.
    • 응답 헤더의 Access-Control-Max-Age 필드에 해당 응답이 몇 초간 캐시될 수 있는지를 담는다.
      • 프리플라이트 응답이 해당 필드 값에 따라 일정 시간 캐시되기 때문에, 브라우저는 프리플라이트 요청을 보내기 전 캐시된 응답이 있나 확인한 다음 없다면 preflight 요청을 보내게 된다.
  4. 서버의 preflight 응답을 받은 브라우저는 응답 내용을 토대로 CORS 정책 위반 여부를 검사하여 실제 요청을 보낼지 결정한다.
  5. 브라우저가 유효한 요청이라 판단했다면, 서버에게 본 요청을 보내고 서버로부터 받은 응답을 클라이언트에 전달한다.

2. 단순 요청 (Simple Request)

특정 조건이 만족되면 preflight 요청을 생략하고 바로 서버에 요청을 보내는 방식이다.

단순 요청을 보내기 위해 만족해야 할 조건은 다음과 같다.

  1. 요청의 메서드는 GET, HEAD, POST 중 하나여야 한다.
  2. 자동으로 설정되는 헤더 외에 Accept, Accept-Language, Content-Language, Content-Type 헤더의 값만 수동으로 설정할 수 있다.
  3. Content-Type 헤더에는 application/x-www-form-urlencoded, multipart/form-data, text/plain 값만 허용된다.

위의 조건을 만족한 단순 요청의 흐름은 다음과 같다.

  1. 클라이언트는 서버에 요청을 보낸다.
  2. 서버는 요청에 대한 응답을 보낸다.
    • 응답 헤더의 Access-Control-Allow-Origin 필드에 허용하는 Origin 목록을 담는다.
  3. 브라우저는 서버의 응답을 토대로 CORS 정책 위반 여부를 검사한다.
  4. 브라우저가 유효한 요청이라 판단했다면 서버의 응답을 클라이언트에 전달한다.

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

요청 헤더에 인증 정보를 담아 보내는 요청 방식이다.

출처가 다를 경우 별도의 설정을 하지 않으면 쿠키를 보낼 수 없으며 클라이언트와 서버 양측 모두 CORS 설정이 필요하다.

  • 클라이언트
    • 요청 헤더에 withCredentials: true를 넣어줘야 한다.
  • 서버
    • 응답 헤더에 Access-Control-Allow-Credentials: true를 넣어줘야 한다.
    • Access-Control-Allow-Origin을 설정할 때 *로 설정하면 에러가 발생한다.


Reference

https://blog.4d.com/support-of-cross-origin-resource-sharing-cors/
https://evan-moon.github.io/2020/05/21/about-cors/

profile
블로그 이사중 🚚 (https://sungjihyun.vercel.app)

0개의 댓글