CORS

김석·2023년 5월 29일
0

Network

목록 보기
8/9

1. CORS에 대한 기본적인 내용

  • CORS 관련 이슈는 모두 COS 정책을 위반했기 때문에 발생.
  • 사실 CORS라는 방어막이 존재하기 때문에 이곳 저곳에서 가져오는 리소스가 안전하다는 최소한의 보장받을 수 있음.
  • CORS: Cross-Origin Resource Sharing, 교차 출처(다른 출처) 리소스 공유.

1-1. 출처(Origin)이란?

출처란 Protocol, Host, 포트를 모두 합친 것.
즉, URL에서 서버의 위치를 찾아가가 위해 필요한 가장 기본적인 것들을 합쳐놓은 것.


1-2. SOP(Same-Origin Policy)

  • 같은 출처에서만 리소스를 공유할 수 있다라는 규칙을 가진 정책.
  • 하지만 웹이라는 오픈된 환경에서는 다른 출처에 있는 리소스를 가져와서 사용하는 일이 굉장히 흔하기 때문에 무작정 막기 힘들다.
  • 이에 따라 몇 가지 예외 조항을 두고, 이 조항에 해당하는 리소스 요청은 출처가 다르더라도 허용하기로 함.
  • 그 중 하나가 CORS 정책을 지킨 리소스 요청임.
  • 다른 출처로 리소스를 요청한다면 SOP 정책을 위반한 것이 됨.
  • SOP의 예외 조항인 CORS 정책까지 지키지 않는다면 아예 다른 출처의 리소스를 사용할 수 없음.

1-3. 이러한 정책을 만드는 이유

  • 출처가 다른 두 개의 어플리케이션이 마음대로 소통할 수 있는 환경이 위험하기 때문.
  • 클라이언트 어플리케이션, 특히 웹은 사용자의 공격에 너무나도 취약하다.
  • 개발자 도구만 열어도 각종 정보들을 아무런 제재 없이 열람할 수 있음.
  • 다른 출처의 어플리케이션이 서로 통신하는 것에 대해 아무런 제약도 존재하지 않는다면, 공격자가 사용자의 정보를 탈취하기 너무나도 쉬워진다.

1-4. 같은 출처와 다른 출처의 구분

Scheme, Host, Port가 같으면 같은 출처라고 판단한다.

https://seok0301.com:80 과 같은 출처로 인정되는 예시

// 같은 출처. scheme, host, port가 같음.
https://seok0301.com/about

// 같은 출처. scheme, host, port가 같음.
https://seok0301.com/about?key=value

// 다른 출처. scheme이 다름.
http://seok0301.com

// 다른 출처. host가 다름.
https://seok0301.co.kr

// 다른 출처. host가 다름.
https://seok990301.com

// 다른 출처. port가 다름.
https://seok0301.com:8080
  • 출처를 비교하는 로직은 서버에 구현된 것이 아니라 웹 브라우저에 구현되어 있음.
  • 즉 웹 브라우저의 JavaScript 엔진에서 처리함.
  • 만약 우리가 CORS 정책을 위반하는 리소스 요청을 하더라도,해당 서버가 같은 출처에서 보낸 요청만 받겠다는 로직을 가지고 있는 경우가 아니라면, 서버는 정상적으로 응답하고, 이후 브라우저가 이 응답을 분석해서 CORS 정책 위반이라고 판단되면 그 응답을 사용하지 않고 그냥 버리는 순서로 진행된다.
  • 따라서 브라우저를 통하지 않고 서버 간 통신을 할 때는 이 정책이 적용되지 않는다.
  • 또한 CORS 정책을 위반하는 리소스 요청 때문에 에러가 발생했다고 해도, 서버 쪽 로그에는 정상적으로 응답을 했다는 로그만 남는다.

2. CORS 동작 원리

CORS가 동작하는 방식은 한 가지가 아니라 세 가지 시나리오에 따라 변경됨.


2-1. 기본적인 흐름

  1. 웹 클라이언트 어플리케이션이 다른 출처의 리소스를 요청할 때, HTTP 프로토콜을 사용해서 요청을 보냄.
  2. 브라우저는 요청 헤더에 Origin이라는 필드에 요청을 보내는 출처를 함께 담아서 보냄.
  3. 웹 서버는 이 요청에 대한 응답을 할 때, 응답 헤더의 Access-Control-Allow-Origin 값에 "이 리소스를 접근하는 것이 허용된 출처"를 내려줌.
  4. 이후 응답을 받은 브라우저는 자신이 보냈던 요청의 Origin과 서버가 보내준 응답의 Access-Control-Allow-Origin을 비교한 후, 응답이 유효한지 아닌지 판단함.

2-2. 시나리오 1: Preflight Request

  • 웹 어플리케이션을 개발할 때 가장 흔하게 마주치는 시나이로.
  • 브라우저는 요청을 한 번에 보내지 않고, 예비 요청과 본 요청으로 나누어 서버에 전송함.
  • Preflight: 브라우저가 본 요청을 보내기 전에 보내는 예비 요청.
  • 예비 요청에는 HTTP 메소드 중 OPTIONS가 사용됨.
  • 본 요청을 보내기 전에 브라우저 스스로 이 요청을 보내는 것이 안전한지 확인하는 것.

  1. 자바스크립트의 fetch API를 사용하여 브라우저에게 리소스를 받아오라는 명령을 내림.
  2. 브라우저는 서버에게 예비 요청을 먼저 보냄.
  3. 서버는 이 예비 요청에 대한 응답으로 현재 자신이 어떤 것들을 허용하고, 어떤 것들을 금지하고 있는지에 대한 정보를 응답 헤더에 담아서 브라우저에게 다시 보내줌.
  4. 이후 브라우저는 자신이 보낸 예비 요청과 서버가 응답에 담아준 허용 정책을 비교한 후, 이 요청을 보내는 것이 안전하다고 판단되면 같은 엔드포인트로 다시 본 요청을 보냄.
  5. 이후 서버가 이 본 요청에 대한 응답을 하면 브라우저는 최종적으로 이 응답 데이터를 자바스크립트에게 넘겨줌.
  • 실제로 브라우저가 보낸 요청을 보면, 단순히 Origin에 대한 정보 뿐만 아니라, 예비 요청 이후에 보낼 본 요청에 대한 다른 정보들도 함께 포함되어 있다.
  • 예비 요청에서 브라우저는 Access-Control-Request-Headers를 사용해서 본 요청에서 Content-Type 헤더를 사용할 것을 알려주거나, Access-control-request-Method를 사용해서 GET 메소드를 사용할 것을 서버에게 미리 알려줄 수 있다.
  • 예비 요청에 대해서 서버는 응답을 보낸다.
  • 서버가 보내준 응답 헤더에는 Access-Control-Allow-Origin 값이 존재한다.
  • 이 값은 서버의 리소스에 접근이 가능한 출처를 나타낸다.
  • 브라우저는 이 값과 요청을 보낸 출처를 비교하고, 만약 요청을 보낸 출처가 접근 가능한 출처가 아닐 경우 CORS 정책을 위반했다는 에러 메시지를 출력한다.
  • 예비 요청에 대한 응답 코드는 200이고, 콘솔에는 에러가 표시될 수 있다.
  • CORS 정책 위반으로 인한 에러는 예비 요청의 성공 여부와 별 상관이 없다.
  • 브라우저가 CORS 정책 위반 여부를 판단하는 시점은 예비 요청에 대한 응답을 받은 이후이기 때문.
  • 중요한 것은 예비 요청의 성공/실패 여부가 아니라, "응답 헤더에 유효한 Access-Control-Allow-Origin 값이 존재하는가" 이다.
  • GET 메소드이면 큰 일이 일어나지 않겠지만, PATCH나 DELETE라면? CORS 에러가 발생했음에도 이미 서버는 요청을 모두 처리했기 때문에 지우면 안되는 데이터까지 수정하게 된다.
  • Preflight Request를 사용한다면 해당 요청은 사전 요청이기 때문에 서버는 어떠한 행동도 취하지 않는다.

2-3. 시나리오 2: Credentialed Request

  • 기본적으로 브라우저가 제공하는 비동기 리소스 요청 API인 XMLHttpRequest나 fetch API는 별도의 옵션 없이 브라우저의 쿠키 정보나 인증과 관련된 헤더를 함부로 요청에 담지 않는다.
  • 이때 요청에 인증과 관련된 정보를 담을 수 있게 해주는 것이 credentials 옵션이다.
  • credentials 옵션의 값과 의미.
    • same-origin(기본값): 같은 출처 간 요청에만 인증 정보를 담을 수 있음.
    • include: 모든 요청에 인증 정보를 담을 수 있음.
    • omit: 모든 요청에 인증 정보를 담지 않음.
  • 만약 same-origin이나 include 같은 옵션을 사용해서 리소스 요청에 인증 정보가 포함된다면, 브라우저는 다른 출처의 리소스를 요청할 때 단순히 Access-Control-Allow-Origin만 확인하는 것이 아니라, 더 빡빡한 검사 조건을 추가하게 됨.
  • 만약 웹 서버가 Access-Control-Allow-Origin 값으로 모든 출처를 허용한다는 의미인 *로 설정했다면, 다른 출처에서 해당 서버로 리소스를 요청할 때 CORS 정책 위반으로 제약을 받지 않는다.
  • 이러한 경우 localhost와 같은 로컬 개발 환경에서도 fetch API를 사용하여 마음대로 리소스를 요청하고, 받아올 수 있다.
  • 만약 credentials 값을 변경하지 않고, 로컬 환경에서 다른 출처의 웹 서버로 리소스 요청을 보낸다면, 기본 값은 same-origin이기 때문에 인증 정보가 포함되지 않는다.
  • 이에 따라 브라우저는 응답의 "Access-Control-Allow-Origin: *"라는 값만 보고, 안전한 요청으로 판단함.
  • 만약 credentials 값을 include로 바꾸고, 로컬 환경에서 다른 출처의 웹 서버로 리소스 요청을 보낸다면, 브라우저는 동일 출처가 아님에 상관 없이 인증 정보가 포함된 요청을 보낸다.
  • 이러한 상황에서 브라우저는, 인증 모드가 include일 경우 "Access-Control-Allow-Origin: *"을 사용하면 안 된다는 에러 메시지를 출력한다.
    -credentials 값이 include인 경우, Access-Control-Allow-Origin에는 *를 사용할 수 없으며, 응답 헤더에는 반드시 Acces-Control-Allow-Credentials: true가 존재해야 한다.

3. CORS를 해결할 수 있는 방법

3-1. 서버에서 Access-Control-Allow-Origin 헤더에 알맞은 값 세팅

  • 와일드 카드인 *을 사용하면 모든 출처에서 오는 요청을 받을 수 있음.
  • 하지만 당연하게도 보안적으로 심각한 이슈가 발생할 수 있음.
  • 따라서 허용할 출처를 명시하는 것이 좋음.
  • Access-Control-Allow-Origin 헤더는 미들웨어에서 세팅하는 것이 편함.
  • 백엔드 프레임워크의 경우에는 CORS 설정을 위한 세팅이나 미들웨러 라이브러리를 제공함.

3-2. proxy 설정

  • proxy 서버란 컴퓨터 네트워크에서 다른 서버상의 자원을 찾는 클라이언트의 요청을 받아 중계하는 서버.
  • 주로 보안상의 문제로 직접 통신을 송수신할 수 없는 상황에서 프록시를 이용하여 중계 통신함.
  • 클라이언트가 자신을 통해서 다른 네트워크 서비스에 간접적으로 접속할 수 있게 해줌.
  • webpack-dev-server를 사용해서 개발 환을 구축한 경우, 해당 라이브러리가 제공하는 proxy 기능을 사용.
module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'https://seok0301.com',
        changeOrigin: true,
        pathRewrite: { '^/api': '' },
      },
    }
  }
}
  • 로컬 환경에서 /api로 시작하는 URL로 보내는 요청에 대해서 브라우저는 loalhost:8000/api로 요청을 보낸 것으로 알고 있지만, 사실 뒤에서 webpack이 https://seok0301.com으로 요청을 proxying해주기 때문에 마치 CORS 정책을 지킨 것처럼 브라우저를 속이면서 원하는 서버와 자유롭게 통신할 수 있다.

  • 실제 프로덕션 환경에서도 클라이언트의 어플리케이션의 소스를 서빙하는 출처와 API 서버의 출처가 같은 경우에만 사용하는 것이 좋다.
    -어플리케이션을 빌드하고 서버에 올리고 나면 더 이상 webpack-dev-server가 구동하는 환경이 아니기 때문.


출처

https://evan-moon.github.io/2020/05/21/about-cors/
https://fgh0296.tistory.com/16
https://velog.io/@kim-jaemin420/%EC%9B%B9%ED%8C%A9%EC%9B%B9%ED%8C%A9%EC%9D%B4%EB%9E%80-%EC%9B%B9%ED%8C%A9%EC%9D%B4-%ED%95%98%EB%8A%94-%EC%9D%BC%EA%B3%BC-%ED%95%84%EC%9A%94%ED%95%9C-%EC%9D%B4%EC%9C%A0
https://fullmoon1344.tistory.com/145

profile
handsome

0개의 댓글