CORS

김동현·2021년 7월 16일
0

Interation With Server

목록 보기
6/7

  • Protocol + Host + Port Number
  • Port Number는 생략 가능(HTTP: 80, HTTPS: 443 으로 기본적으로 정해져 있기 때문)

CORS (Cross-Origin Resource Sharing)

내가 허용한 오리진만 허용한다.

차음 전송되는 리소스의 도메인과 다른 도메인으로부터 리소스가 요청될 경우 해당 리소스는 cross-origin HTTP 요청에 의해 요청된다.

예를 들어, http://domain-a.com 으로부터 전송되는 HTML 페이지가 src 속성을 통해 http://domain-b.com/image.jpg 를 요청하는 경우가 있다.

오늘날 많은 웹 페이지들은 CSS 스타일시트, 이미지, 그리고 스크립트와 같은 리소스들을 각각의 출처로부터 읽어온다.

한 도메인에서 로드되어 다른 도메인에 있는 리소스와 상호작용하는 클라이언트 웹 에플리케이션에 대한 방법을 정의

도메인이나 포트 프토로콜이 다른 서버에 리소스를 요청하는 것

브라우저에서 보안 상의 이유로 스크립트에서 시작한 교차 출처 HTTP 요청을 제한한다고 한다.

즉 이 API를 사용하는 웹 애플리케이션은 자신의 출처와 동일한 리소스만 불러올 수 있는 것인데 이것을 동일 출처 정책(Same-Origin Policy)

고로 다른 출처의 리소스를 불러오려면 그 출처에서 올바른 CORS 헤더를 포함한 응답을 반환해야 한다.

웹 서버를 통해서 클라이언트 브라우저의 앱(웹 어플리케이션)의 사용자를 보호하기 위한 브라우저 만의 자발적인 보안 조치(그러지 않을 수도 있다)

SOP(Same-Origin Policy)

CORS의 필요성

기본적으로 보안상의 이유로 브라우저는 Same-Origin Policy(SOP, 동일 출처 정책)을 따른다.

그렇기 때문에 XMLHttpRequestfetch API는 동일 출처 정책에 따라 동일한 출처의 리소스만을 요청할 수 있다.

하지만 웹 어플리케이션을 구현하다보면, 다른 출처에 있는 리소스를 요청하는 경우가 많다.

일례로, SPA(Single Page Application)로 구현된 웹 어플리케이션에서 다른 도메인을 가진 API 서버에 요청하는 경우는 매우 비일비재하다.

또한, web font을 요청하는 경우도 마찬가지이다.

cross-origin 요청을 필요한 경우가 많아지고 있기 때문에, 이를 안전하게 처리할 정책이 필요했기에 CORS가 나오게 됬다.

CORS는 cross-origin 요청을제한적으로 허용함으로써 좀 더 안전하게 cross-origin 요청을 처리할 수 있는 정책이다.

CORS 동작

Simple Request

-> Preflight 를 트리거하지 않는 일부 요청

Preflight Request

-> 사전 전송, 안전한지 확인

Credential Request

-> 인증정보를 포함한 요청

  • 웹 클라이언트 어플리케이션이 다른 출처의 리소스를 요청할 때는 HTTP 프로토콜 사용

1) 브라우저 : 요청 헤더에 Origin 이라는 필드에 요청을 보내는 출처를 함께 담아 보냄Origin : https://velog.io/

2) 서버 : 해당 요청에 대한 응답시 응답 헤더의 Access-Control-Allow-Origin 이라는 필드에 리소스를 접근하는 것이 허용된 출처를 내려줌

3) 브라우저 : 보냈던 요청의 Origin과 서버가 보내준 응답의 Access-Control-Allow-Origin를 비교하여 유효성 체크

CORS reponse

CORS 요청이 온다면, 서버는 Access-Control-* 헤더를 메시지에 포함시킬 수 있다.

  • Access-Control-Allow-Origin : 요청을 허용할 출처를 명시할 때 사용하며, *을 사용하면 모든 출처의 리소스 요청을 허용합니다.
  • Access-Control-Allow-Methods : 어떤 메서드를 허용할 것인지 명시합니다.
  • Access-Control-Allow-Headers : 어떤 헤더들을 허용할 것인지 명시합니다.
  • Access-Control-Max-Age : preflight 요청에 대한 응답을 브라우저에서 얼마만큼 캐싱하고 있을지 설정할 때 사용합니다.
  • Access-Control-Expose-Headers : 브라우저가 스크립트에 노출시킬 헤더의 목록을 명시할 때 사용합니다. 기본적으로 다음 7가지 헤더(일명 CORS safe-listed header)는 따로 설정하지 않아도 노출시킵니다.
    • Cache-Control
    • Content-Language
    • Content-Length
    • Content-Type
    • Expires
    • Last-Modified
    • Pragma

서버는 위와 같은 헤더를 통해 리소스에 대한 CORS 요청을 어느 수준까지 허용할 것인지 클라이언트(브라우저)에게 알려주고, 이를 바탕으로 브라우저는 추후동작을 결정한다.

CORS request

CORS 요청을 보낼 때, 브라우저는 해당 요청을 두 가지 방식으로 처리한다.

Preflight request를 사용한 방식과 그렇지 않은 방식(Simple request)이다.

Simple request

다음과 같은 조건을 만족하면, 브라우저는 CORS 요청을 Simple request로 처리한다.

  • GET, POST, HEAD
  • 유저 에이전트가 자동으로 설정한 헤더
  • Content-Type 헤더 값으로 다음과 같은 값을 사용하였을 때
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain
  • XMLHttpRequestUpload 객체에 이벤트 리스너가 등록되지 않은 경우
  • 요청에서 ReadableStream 객체를 사용하지 않은 경우

이 방식은 일반 요청이랑 동일하게 동작하며, 단 하가지 다른 점은 응답의 Access-Control-Allow-Origin 에 따라 브라우저가 응답을 정상적으로 처리할지를 결정한다.

에를 들어 http://localhost:8080 에서 서버(http://localhost:9000/api/menus)에 요청을 보낸다면, 다음과 같은 요청 메시지가 전송된다.

간단하게 Origin 헤더만 추가해서 보내준다.

GET /api/menus HTTP/1.1

Host: localhost:9000
Referer: http://localhost:8080/
Origin: http://localhost:8080
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
...

아래 응답에 Access-Control-Allow-Origin 헤더가 존재하지 않기 때문에 브라우저는 이 응답을 자바스크립트 코드에 노출시키지 않고 오류를 뱉어낸다.

HTTP/1.1 200 OK

Content-Type: application/json; charset=utf-8
Content-Length: 155
...
// 교차 출처 요청 차단: 동일 출저 정책으로 인해 http://localhost:9000/api/menus 에 있는
// 원격 자원을 차단하였습니다.
// 원인: 'Access-Control-Allow-Origin' CORS 헤더가 없음.

서버에 Access-Control-Allow-Origin 헤더를 추가해서 응답하면 다음과 같이 정상적으로 자바스크립트 코드에서 응답을 처리할 수 있다.

HTTP/1.1 200 OK

Access-Control-Allow-Origin: http://localhost:8080 // 이 부분
Content-Type: application/json; charset=utf-8
Content-Length: 155
...

Preflight request

  • 위에서 요청한 simple request와는 달리 먼저 OPTIONS 메서드를 통해 다른 도메인의 리소스로 HTTP 요청을 보내 실제 요청이 전송하기에 안전한지 확인한다.
  • Cross-site 요청은 유저 데이터에 영향을 줄 수 있기 때문에 이와같이 미리 전송(preflighted)한다.
  • 또한 다음 조건 중 하나라도 사용하는 경우 실행이 된다.
    • PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH
  • request가 다른 헤더들을 추가되는 경우에도 preflight가 실행됨
  • 아래 값이 나오면 무조건 preflight 가 실행된다.
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

Simple request 가 아니면 브라우저는 실제 요청을 보내기 전에 한 가지 작업을 수행한다.

Preflight request 를 실제 요청 메시지보다 서버에 먼저 보낸다.

서버는 이에 대한 응답을 주고, 브라우저는 이를 통해 실제 요청을 보낼지 결정한다.

Preflight requestOPTIONS 메서드를 사용하며, 다음과 같은 요청 헤더를 사용한다.

  • Access-Control-Request-Method: 실제 요청에서 사용하는 메서드를 서버가 알 수 있도록

    설정하는 헤더

  • Access-Control-Request-Headers: 실제 요청에 포함될 헤더를 서버가 알 수 있도록

    설정하는 헤더

  • Origin: 요청을 보낸 출처로, URL 중 scheme과 host, port만 명시

보통 Preflight request는 브라우저가 자동적으로 생성하기 때문에 크게 신경 쓸 필요는 없지만, 각 헤더가 무엇을 의미하는지는 알아둘 필요가 있다.

응답이 오면, 브라우저는 방금 언급한 3가지 헤더 값과 응답의 Access-Control-* 헤더의 값을 비교하며 실제 요청을 서버에게 보낼지 결정한다.

예를 들어, 다음과 같은 요청을 보낸다면 Simple request의 조건을 만족하지 못하므로, 실제 요청을 보내기 전에 Preflight request를 보낸다.

  • Preflight request
OPTIONS /api/menus HTTP/1.1

Host: localhost:9000
Access-Control-Request-Method: GET
Access-Control-Request-Headers: content-type
Referer: http://localhost:8080/
Origin: http://localhost:8080
...

서버가 다음과 같이 응답을 했다면, 브라우저는 실제 요청을 보내도 문제가 없다고 판단하여 실제 요청을 서버에 보낸다. 이후 과정은 일반적인 요청-응답 사이클과 같다.

  • Preflight request에 대한 응답 - 성공
HTTP/1.1 200 OK

Access-Control-Allow-Origin: http://localhost:8080
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: -1
Connection: keep-alive
Content-Length: 0
  • 실제 요청을 서버로 전송
GET /api/menus HTTP/1.1

Host: localhost:9000
Referer: http://localhost:8080/
Content-Type: application/json
Origin: http://localhost:8080
...

하지만 서버가 다음과 같은 응답을 했다면, 브라우저는 실제 요청을 보내지 않고 오류 메시지를 뱉어낼 것이다.

  • Preflight request에 대한 응답 - 실패
HTTP/1.1 200 OK

Access-Control-Allow-Origin: http://localhost:8080
Access-Control-Max-Age: -1
Connection: keep-alive
Content-Length: 0
// 교차 출처 요청 차단: 동일 출처 정책으로 인해 http://localhost:9000/api/menus 에 있는
// 원격 자원을 차단하였습니다.
// 원인: CORS 사전 점검 응답의 헤더 'Access-Control-Allow-Headers'에 따라
// 헤더 'Content-Type'가 허용되지 않음

// 교차 출처 요청 차단: 동일 출처 정책으로 인해 http://localhost:9000/api/menus 에 있는
// 원격 자원을 차단하였습니다.
// 원인: CORS 요청이 성공하지 못함

Credential request

  • HTTP cookies 와 HTTP Authentication 정보를 인식
  • 기본적으로 cross-site XMLHttpRequest 나 Fetch 는 자격증명을 보내지 않고 좀 더 보안을 강화하고 싶을 때 사용함
  • 인증과 관련된 정보를 담을 수 있게 해줌
  • credentials 옵션
    • same-origin (기본값) : 같은 출처 간 요청에만 인증 정보를 담을 수 O
    • include : 모든 요청에 인증 정보를 담을 수 O
    • omit : 모든 요청에 인증 정보를 담지 X
  • CORS 정책 위반 검사 추가되는 룰1) Access-Control-Allow-Origin에는 *를 사용할 수 없으며, 명시적인 URL이어야한다.2) 응답 헤더에는 반드시 Access-Control-Allow-Credentials: true가 존재해야한다.

브라우저가 자발적으로 simple request가 아닌 이상 preflight request를 서버에 날린다.

허용이 되면 그다음으로 실제 원랬던 request가 날라간다.

reponse를 받아서 이제 onload() 나 onerror()를 확인

이게 CORS의 핵심

profile
개발자로서의 첫걸음

0개의 댓글

관련 채용 정보