[보안]CORS(Cross-Origin Resource Sharing)

·2024년 4월 26일

보안

목록 보기
4/8

✔️ CORS란?

  • 교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제이다.
  • 웹 애플리케이션은 리소스가 자신의 출처(도메인, 프로토콜, 포트)와 다를 때 교차 출처 HTTP 요청을 실행한다.
  • 교차 출처 요청의 예시: https://domain-a.com의 프론트 엔드 JavaScript 코드가 XMLHttpRequest를 사용하여 https://domain-b.com/data.json을 요청하는 경우.
  • 보안 상의 이유로, 브라우저는 스크립트에서 시작한 교차 출처 HTTP 요청을 제한한다.
  • 예를 들어, XMLHttpRequest와 Fetch API는 동일 출처 정책 따른다. 즉, 이 API를 사용하는 웹 애플리케이션은 자신의 출처와 동일한 리소스만 불러올 수 있으며, 다른 출처의 리소스를 불러오려면 그 출처에서 올바른 CORS 헤더를 포함한 응답을 반환해야 한다.
    ▶️ 여기서 origin(출처) 이란 scheme(protocol), host(domain), port 로 구성된다.
    예를들어, https://www.google.com/maps 라는 주소가 있다고하면 protocolhttps:// host는 www.google.com port는 :443 이다.
    ▶️ 동일 출처(Same Origin) 란 scheme, host, port 가 모두 같을때를 말한다.

  • CORS 체제는 브라우저와 서버 간의 안전한 교차 출처 요청 및 데이터 전송을 지원한다.
  • 최신 브라우저는 XMLHttpRequest 또는 Fetch와 같은 API에서 CORS를 사용하여 교차 출처 HTTP 요청의 위험을 완화한다.
  • CORS는 다른 출처의 리소스가 필요한 경우, SOP를 우회하기 위한 여러가지 방법 중 가장 권장되는 방법이다.

✔️ CORS 동작 방법

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

  • Preflight Request는 요청을 예비 요청본 요청으로 나눈다.
  • OPTIONS 메서드를 통해 다른 도메인의 리소스에 요청이 가능한지 (실제 요청이 전송하기에 안전한지) 확인 작업을 하고, 요청이 가능하다면 실제 요청을 보낸다.
  • Cross-origin 요청은 유저 데이터에 영향을 줄 수 있기 때문에 Preflight 요청을 한다.

클라이언트와 서버간의 첫 번째 통신인 preflight request/response 를 살펴보자.

💠 Preflight Request

  • OPTIONS 요청과 함께 두 개의 다른 요청 헤더가 전송된다.
  • 아래에서 첫 행은 실제 요청을 전송할 때 POST 메서드로 전송된다는 것이고,
    두번째 행은 실제 요청을 전송 할 때 X-PINGOTHER 와 Content-Type 사용자 정의 헤더와 함께 전송된다는 것을 서버에 알려준다.
Access-Control-Request-Method: POST	# 실제요청의 메서드
Access-Control-Request-Headers: X-PINGOTHER, Content-Type # 실제요청의 추가헤더

💠 Preflight Response

  • 서버가 메서드와 헤더를 받을 수 있음을 알려준다.
  • 마지막행은 preflight request에 대한 응답을 캐시할 수 있는 시간(초)이다.
  • 프리플라이트를 보내면 사전, 실제 요청 두번이 매번 왔다갔다 하므로 브라우저가 캐싱을 해두고 똑같은 요청을 보낼때, 사전 요청을 보내지 않고 바로 본 요청을 보낸다.
Access-Control-Allow-Origin: http://foo.example	# 서버측 허가출처
Access-Control-Allow-Methods: POST, GET, OPTIONS # 허가 메서드
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type	# 서버측 허가헤더
Access-Control-Max-Age: 86400 # Prefilght 응답 캐시기간
  • preflight request가 완료되면 실제 요청을 전송한다.

2. 단순 요청 (Simple Request)


Simple Request는 Preflight Request와 다르게 요청을 보내면서 즉시 cross origin인지 확인하는데, 다음 조건을 모두 충족해야한다.

  • 메서드는 GET POST HEAD 중 하나
  • 헤더는 Accept, Accept-Language, Content-Language, Content-Type 만 허용
  • Content-Type 헤더는 다음의 값들만 허용
    ▶️ application/x-www-form-urlencoded
    ▶️ multipart/form-data
    ▶️ text/plain

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

  • 인증 관련 헤더를 포함할 때 사용하는 요청이다.
  • 브라우저가 제공하는 비동기 리소스 요청 API인 XMLHttpRequest 객체나 fetch API는 별도의 옵션 없이 브라우저의 쿠키 정보나 인증과 관련된 헤더를 기본적으로 요청에 담지 않으므로, credentials 옵션을 변경하지 않고서는 cookie를 주고 받을 수 없다.

옵션은 세가지가 있다.

  1. omit : 절대로 cookie 들을 전송하거나 받지 않는다.
  2. same-origin : 동일 출처(same origin)이라면, user credentials (cookies, basic http auth 등..)을 전송한다. (default 값)
  3. include : cross-origin 호출이라 할지라도 언제나 user credentials (cookies, basic http auth 등..)을 전송한다.

🌟 <예시>

fetch("주소", {
  credentials: "include", // 모든 요청에 인증 정보 포함
});
  • axios 로 통신할 시, withCredentials 설정을 true 로 넣어주면 된다.
axios.post(주소, 데이터, { withCredentials: true });

// 또는 공통으로 추가
axios.defaults.withCredentials = true;
  • 또한 credentials 설정을 include/true 로 설정하면 CORS정책에 의해 Access-Control-Allow-Origin을 모든 출처를 허용하는 '*' 로 지정할 수 없다는 에러가 발생하며, 따라서 cors 설정에서 *을 입력하여 모든 출처를 허용한 경우에는 특정 출처를 정확히 명시해야 한다.

✔️ CORS 해결 방법

  • CORS 정책 위반으로 에러가 발생했을때 해결하는 방법을 알아보자.

1. Access-Control-Allow-Origin 응답 헤더 세팅

  • 서버측 응답에서 접근 권한을 주는 헤더를 추가하여 해결
app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin", "*"); // 모든 도메인
  res.header("Access-Control-Allow-Origin", "https://example.com"); // 특정 도메인
});

2. cors 모듈 사용

  • 아무 옵션없이 설정하면 모든 cross-origin 요청에 대해 응답이므로, 특정 도메인이나 특정 요청에만 응답하게 옵션을 설정하는 것이 좋다.

◾ 특정 도메인 접근 허용

const options = {
  origin: "http://example.com", // 접근 권한을 부여하는 도메인
  credentials: true, // 응답 헤더에 Access-Control-Allow-Credentials 추가
  optionsSuccessStatus: 200, // 응답 상태 200으로 설정
};

app.use(cors(options));

◾ 특정 요청 접근 허용

app.get("/example/:id", cors(), function (req, res, next) {
  res.json({ msg: "example" });
});

3. webpack-dev-server proxy 기능

  • 리액트 개발환경에서, 서버쪽 코드를 수정하지 않고 해결할 수도 있다.
  • 아래와 같이 프록시 속성을 설정하면, 서버에서 해당 요청을 받아준다.
// 프록시 쓰지 않았을때
// localhost:8080(클라이언트 측) --X (CORS)--> domain.com (서버 측)

// 프록시를 설정 후
// localhost:8080(클라이언트 측) --O 프록시가 설정된 Webpack Dev Server--> domain.com (서버 측)

module.exports = {
  devServer: {
    proxy: {
      "/api": {
        target: "domain.com",
        changeOrigin: true,
      },
    },
  },
};
  • 중간의 프록시 서버 덕분에, domain.com 서버에서는 같은 도메인(domain.com)에서 온 요청으로 인식하여 CORS 에러가 발생하지 않는다.

4. package.json에 proxy값을 설정

  • create-react-app 으로 생성한 프로젝트에서는, package.json 에 proxy 값을 설정하여 proxy 기능을 활성화 하는 방법도 있다.
{
  //...
  "proxy": "http://localhost:4000"
}

참고글 1 https://developer.mozilla.org/ko/docs/Web/HTTP/CORS
참고글 2 https://ingg.dev/cors/#cors_background

profile
백엔드 개발자가 되고 싶어요(22.8.15~)

0개의 댓글