본문 바로가기
📟 Computer science/Network

[ Network ] CORS란 무엇일까? 어떻게 허용할까? (feat. SOP)

by 깸뽀 2022. 12. 24.
728x90

📌 Same-Origin-Policy ( SOP | 동일 출처 정책)

동일 출처 정책은 웹 브라우저에서 보안을 강화하기 위해 다른 Origin으로 요청을 보낼 수 없도록 금지하는 브라우저의 기본적인 보안정책이다. 즉, 동일한 Origin으로만 요청을 보낼 수 있게 하는 것이다.

* 자바스크립트엔진 표준 스펙의 보안 규칙

 

✋ 출처(Origin)란?
    쉽게 말하면 URL 주소이다. 즉, 프로토콜, 호스트, 포트 번호를 합친 부분을 의미한다.
   ex) https://boboshop.com:8080

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

 

 

 

SOP 사용 이유 

SOP가 없어 자유롭게 다른 Origin으로 요청을 보낼 수 있다면?

악의적인 마음을 품은 해커가 자신의 웹사이트를 구축해놓고, 이 웹사이트를 가리키는 링크를 담은 메일을 사용자에게 보내는 것이다. 사용자가 그 링크를 클릭했을 시, 해커가 심어둔 JavaScript 코드가 실행되어 자기도 모르게 A 웹사이트로 개인 정보를 조회하는 API 요청을 보낼 것이다. 그러면 해커는 사용자의 개인정보를 탈취하여 빼돌릴 수 있게 된다.

(👉 CSRF공격)

  • SOP를 적용하면 공격받을 수 있는 경로를 제한 할 수 있고,
  • 해로운 문서 및 스크립트를 분리 할 수 있다.

 

동일 출처(Origin)의 조건

  • 프로토콜
  • 호스트(도메인)
  • 포트번호 (명시한 경우)

 

 

📌 Cross-Origin-Resource-Sharing ( CORS | 교차 출처 리소스 공유 )

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

 

CORS의 필요성

1) 프론트와 백엔드의 서버 분리

현대 웹 애플리케이션은 빠른 서비스를 위해 서버가 크게 프론트, 백으로 나누어졌다.

클라이언트 측에서 요청을 하는 순간 프론트 서버의 응답을 받아 랜더링 되고,

데이터는 백엔드 서버에 요청하여 받아오게 되는 흐름은 SOP 때문에 처리의 한계를 가진다.

👉 교차 출처 요청 예시

http://localhost:3000의 프론트엔드 프로젝트에서 백엔드 프로젝트인 http://localhost:8080에 요청하는 경우

 

2) 다른 출처의 데이터 사용

웹 구현시 다른 출처의 데이터를 요청해 써야하는 경우가 많아졌다.

 

3) 마이크로 서비스 아키텍처

트래픽양이 증가하면서 서버 한대로는 물리적으로 버티기 힘들어졌고 여러개 서버의 필요성이 대두되었다.

A 서버는 로그인 관리를 위한 서버, B서버는 주문만 받는 서버처럼 하나의 서버가 특정 기능만을 수행할 수 있도록 분할하여 관리된다.

 

 

 

📌 CORS 동작 원리

1. 클라이언트 HTTP 요청

 클라이언트에서 다른 출처로 리소스를 요청할 때, 브라우저는 request 헤더의 'Origin' 이라는 필드에 본인 URL을 담아 보낸다.

 

2. Server 응답

서버가 이 요청에 대한 응답을 할 때, Response(응답) 헤더의 'Access-Control-Allow-Origin' 필드에 접근이 허용된 Origin을 담아 브라우저에게 응답을 보낸다.

* 애스터리스크( * )는 모두 허용한다는 뜻이다.

* origin(*) ▶ 개발 시 모두 열어두고 사용하기도 하며 이를 화이트 리스크라 한다.

 

3. 비교

Response(응답)를 받은 브라우저는 자신이 보냈던 요청의 'Origin' 과 서버가 보내준 Response(응답)의 'Access-Control-Allow-Origin'값을 비교 후 두 개가 동일하다면 유용한 응답이라 판단한다. 유효하지 않다면 해당 응답은 버린다.

 

 

 

 

📌 CORS 접근제어 시나리오

1. 단순 요청 (Simple Request)

Preflight 요청 없이 단 한 번의 요청만을 전송한다. Simple Request는 아래와 같은 조건을 만족해야한다.

아래와 같은 조건을 만족하는 단순 요청은 안전한 요청으로 취급된다.

💡 메서드: GET, POST, HEAD 중 하나
💡 User Agent가 자동으로 설정한 헤더를 제외하면, 아래와 같은 헤더들만 사용할 수 있다.
    ◾ Accept
    ◾ Accept-Language
    ◾ Content-Language
    ◾ Content-Type
    ◾ DPR
    ◾ Downlink (en-US)
    ◾ Save-Data
    ◾ Viewport-Width
    ◾ Width
💡 Content-Type은 아래 셋 중 하나여야 한다.
    ◾ application/x-www-form-urlencode
    ◾ multipart/form-data
    ◾ text/plain

 

출처 - https://it-eldorado.tistory.com/163

 

 

 

 

 

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

단순 요청의 조건에 벗어나는 요청의 경우, 서버에 실제 요청을 보내기 전에 예비요청에 해당하는 플라이트 요청을 먼저 실어서 실제 요청이 전송하기에 안전한지 확인한다. 안전한 요청이라고 확인 되면, 실제요청을 서버에게 보낸다.

따라서 총 두 번의 요청을 전송한다.

* 사전요청은 options메서드를 통해  요청을 확인한다.

 

조금 더 구체적으로 말하면 헤더에 Access-Control-Request-Method를 통해 요청하는 HTTP 메서드 GET,POST,PUT,DELETE 중 하나의 메서드와 Access-Control-Request-Headers를 통해 OPTIONS라는 헤더를 넣고 요청을 보낸다. 이 때 예비로 확인하는 것뿐이기 때문에 바이데 아무것도 작성하지 않고 보낸다.

 

 

Preflight 요청이 필요한 이유

서버는 CORS 위반 여부와 상관없이 일단 요청이 들어오면 처리를 하고 응답을 보낸다. 그 응답을 받은 브라우저가 응답 헤더를 확인하고 응답의 파기 여부를 결정하게 된다. GETHEAD와 같은 요청은 단순 조회를 하기 때문에 상관없지만 PUT, DELETE와 같은 메서드는 서버에 부작용(Side Effect)을 야기할 수 있다. 이는 응답이 유효하지 않아 파기한 브라우저의 의사와 상관없이 발생한다. Preflight는 실제 요청이 CORS를 위반하지 않았는지를 미리 확인하고, 부작용으로부터 서버를 보호하기 위해 전송한다. 하지만 POST와 같은 경우 조건만 만족하면 Preflight 요청 대신 단순 요청으로 전송될 수 있으므로 백엔드에서도 이에 대한 처리가 필요하다.

API테스팅 도구에서는 정상적으로 요청하는데 브라우저에서 호출하기만 하면 왜 CORS에러가...?
참고로 Preflight 요청은 브라우저에서 자동으로 전송되므로 프론트엔드 개발자가 직접보낼 필요는 없다.

개발을 하면서 Postman 또는 Swagger를 사용하면 정상적으로 요청이 되는데, 브라우저에서 호출하기만하면 CORS에러가 발생하는 경우를 겪어봤을 것이다. 브라우저와 다르게 API테스팅 도구에서는 기본적으로 Preflight를 보내지 않기 때문이다.

 

Preflight Request

  • 메소드로 OPTIONS를 사용한다.
  • Origin : 헤더에 자신의 Origin을 설정
  • Access-Control-Request-Method : 실제 요청의 메서드
  • Access-Control-Request-Headers : 실제 요청의 추가 헤더

Preflight Response

  • Access-Control-Allow-Origin : 허용되는 Origin들의 목록 혹은 *
  • Access-Control-Allow-Methods : 허용되는 메소드들의 목록 혹은 *
  • Access-Control-Allow-Headers : 허용되는 헤더들의 목록 혹은 * (default: GET, POST)
  • Access-Control-Max-Age : Preflight : 해당 프리플라이트 요청이 브라우저에 캐시 될 수 있는 시간을 초 단위로 설정
    • preflight Request는 실질적으로 2번 요청 (사전 요청, 실제 요청)
    • ⇒ 1번의 호출에 2번 요청되면 리소스 적으로 좋지 않아 preflight응답에 대해서 브라우저는 캐싱을 해둠

여기서 Preflight Response응답 코드는 200대여야하고 Body는 비어있는 것이 좋다.

 

출처 - https://it-eldorado.tistory.com/163

 

 

 

 

 

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

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

🔸 인증정보 : 쿠키(Cookie) 혹은 Authorization 헤더에 설정하는 토큰 값 등

 

쿠키, 토큰과 같이 사용자 식별 정보가 담긴 요청에 대해서는 조금 더 엄격하게 처리한다. 클라이언트는 요청을 보낼 때 credentials 옵션을 별도로 설정해줘야한다. fetch API의 경우 아래와 같은 3가지 옵션이 존재한다.

  • same-origin : 같은 출처 간 요청에만 인증 정보를 담을 수 있다.
  • include : 모든 요청에 인증 정보를 담을 수 있다.
  • omit : 모든 요청에 인증 정보를 담지 않는다.
fetch("http://example.com/", {
  method: "PUT",
  credentials: "include",
})

 

참고로 XMLHttpRequest 혹은 Axios를 사용할 경우 withCredentials 옵션을 true로 설정해 주면 된다.

axios.post(주소, 데이터, {withCredentials: true});

//또는 공통으로 추가
axios.defaults.withCredentials = true;

 

서버는 응답할 때 Access-Control-Allow-Credentials 라는 헤더를 true 로 설정해줘야한다.

이때, Access-Control-Allow-Origin 은 와일드카드가 될 수 없으며, 명확한 출처를 명시해줘야한다.

 

출처 - https://it-eldorado.tistory.com/163

 

 

 

📌 CORS  해결 방법

👉 서버에서 CORS 해결 방법

1. @CrossOrigin 어노테이션 사용하기 속성 값(Origin, allowedHeaders)을 넣어 기본 값을 대체할 수 있다.

2. CorsFilter 사용하기

3. Configuration 클래스를 생성하고 (⇒ implments WebMvcConfigurer) addCorsMappings란 메서드를 사용하여 CORS의 출처 및 설정 관리를 할 수 있다.

 

👉 프론트에서 CORS 해결 방법

1. 프론트 프록시 서버를 설정한다. 프론트 서버에서 백엔드 서버로 요청을 보낼 때 대상의 URL을 변경한다.

 


Header

요청 방법은 요청하는 헤더와 응답하는 헤더를 통해서 이루어 진다.

 

📢 요청 헤더

1. Access-Control-Request-Method

요청을 할 때 어떤 메서드를 사용할 것인지를 알려주는 것이다.

 

2. Access-Control-Request-Headers

요청을 할 때 어떤 헤더를 사용할 것인지 알려주는 것이다.

 

📢 응답 헤더

1. Access-Control-Allow-Origin

리소스에 접근할 수 있도록 허용하는지를 알려주는 것이다.

 

2. Access-Control-Expose-Headers

브라우저에게 접근할 수 있는 리스트들을 알려주는 것이다.

 

3. Access-Control-Max-Age

캐싱되는 시간을 알려주는 것이다.

 

4. Access-Control-Allow-Credentials

크레덴셜이 true일 때 요청할지에 대한 것을 알려주는 것이다.

 

5. Access-Control-Allow-Methods

허용되는 메서드를 알려주는 것이다.

 

6. Access-Control-Allow-Headers

사용 가능한 HTTP 헤더를 알려주는 것이다.

 

 

 

 

 

 

 

📖 참고

728x90

댓글