ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • CORS Policy (교차 출처 리소스 공유 정책)
    컴퓨터 공학/네트워크 2022. 5. 20. 18:43

    CORS란?

    CORS란 Cross-Origin Resource Sharing의 줄임말로, 교차 출처 리소스 공유라고 번역할 수 있다.

    이는 쉽게 풀어서 설명하면 서로 다른(Cross) 출처(Origin)에서 리소스(Resource)를 제공받는(Sharing) 행위를 의미한다.

     

    CORS 정책 위반으로 에러가 발생한다는 뜻은, 서로 다른 출처에서 리소스를 동시에 제공받으려고 시도했기 때문이다.

    이때 왜 이러한 점을 금지하는 정책이 생겼는지는 아래에서 다시 설명한다.

    출처(Origin)

    여기서 말하는 출처란, 리소스를 제공하는 서버의 위치를 의미한다.

     

    웹에서는 이러한 서버의 위치나 리소스의 위치를 나타내기 위해서는 URI라는 방식을 사용하는데, 통합 자원 식별자(Uniform Resource Identifier)라 불리는 URI는 다음과 같은 형태로 분류할 수 있다.

    이때 CORS에 해당하는 출처가 서로 다른지에 대한 판별은 오직 Origin에 해당하는 부분에서만 이루어진다.

    즉, 해당 URI의 Protocol과 Host 그리고 Port번호가 같다면 이는 동일한 출처로 취급된다.

     

    보통 Port의 경우 생략되는 경우가 많은데, 이는 http의 기본 포트는 80, https의 기본 포트는 443으로 정해져 있기 때문이다. 하지만 그럼에도 만약 Protocaol과 Host는 같더라도 Port가 다르다면, 이는 서로 다른 출처로 취급된다.

    Cross-Origin

    이제 간단한 예시를 통해 서로 다른 출처가 어떠한 형태가 있는지를 확인해보겠다.

     

    이 블로그의 주소인 'https://pokycookie.tistory.com/'와 비교하였을 때 어떤 주소가 같은 출처이고, 어떤 주소가 다른 출처인지를 구분해보면 다음과 같다.

    URI Same Origin
    'https://pokycookie.tistory.com/' O
    'https://pokycookie.tistory.com/category/Node%20JS/Express%20JS' O
    'https://pokycookie.tistory.com/manage/newpost/?type=post' O
    'http://pokycookie.tistory.com/' X
    'https://pokycookie.naver.com/' X
    'https://pokycookie.tistory.com:3000/' X

    CORS Policy

    이렇게 요청한 리소스의 위치를 신경 써야 하는 이유는 CORS 정책 때문이다.

     

    웹에서는 다른 출처에서의 리소스 요청을 기본적으로 제한하는데, 이는 이러한 정책이 해커의 공격을 대비하기 위한 최소한의 보안장치이기 때문이다.

    웹 클라이언트 상에서는 사실상 DOM, 소스코드, 리소스와의 통신과 같은 부분들을 제약 없이 확인할 수 있어, 이를 악용하는 사람이 있다면 웹사이트 취약점 공격에 쉽게 당할 수 있다.

     

    따라서 기본적으로 웹서버에서는 자신의 출처와 동일한 리소스만 불러올 수 있다.

     

    하지만 웹 생태계의 특성상 여러 다른 곳의 이미지나 데이터 같은 리소스를 불러와야 하는 경우가 정말 많다.

    이러한 경우까지 모두 제한할 수는 없기 때문에, CORS 정책을 만족하는 리소스의 요청에 한해서는 다른 출처의 리소스 요청이 허용된다.

     

    그렇다면 이제 이러한 CORS 정책이 동작하는 원리에 대해서 알아보자.

    CORS의 동작

    기본적으로 웹 클라이언트에서 리소스를 요청할 때는 HTTP 프로토콜을 이용하는데, 이때 요청 Header의 Origin 필드에 요청을 보내는 쪽의 출처가 자동으로 담긴다. 이후 서버는 해당 요청에 대한 응답을 하면서 응답 Header의 Access-Control-Allow-Origin 필드에 해당 리소스에 접근이 허용된 출처를 보내준다.

     

    이후 웹 브라우저에서는 자신이 보냈던 요청의 Origin값과 서버의 응답의 Access-Control-Allow-Origin값을 비교하여, 해당 응답이 유효한지 아닌지를 결정한다.

     

    추가로 이러한 CORS의 동작이 동작하는 세 가지의 시나리오가 있다. 이는 아래의 블로그에 굉장히 자세히 정리되어 있으니 참고하길 바란다. (사실 완벽히 이해하지 못해서 적지 못했다. 빠른 시일 내에 이해하고 수정하도록 하겠다.)

     

    CORS는 왜 이렇게 우리를 힘들게 하는걸까?

    이번 포스팅에서는 웹 개발자라면 한번쯤은 얻어맞아 봤을 법한 정책에 대한 이야기를 해보려고 한다. 사실 웹 개발을 하다보면 CORS 정책 위반으로 인해 에러가 발생하는 상황은 굉장히 흔해서

    evan-moon.github.io

    브라우저에서 동작

    이러한 CORS는 일종의 정책이다. 즉, 실체화된 무언가를 의미하는 게 아니다.

    따라서 개발자가 직접 설계하여 만드는 백엔드의 경우 이러한 CORS 정책에 걸리지 않는다. 개발자가 해당 명세를 잘 지키는 코드를 백엔드 서버 내에 직접 구현해두지 않았다면 말이다.

     

    따라서 이러한 CORS 정책 위반 여부는 브라우저에서만 보통 판단한다. 보통의 브라우저(Internet Explorer를 제외하고)는 웹 규약을 잘 지키기 때문에 내부적으로 이러한 CORS를 판단하는 장치가 마련되어 있다.

     

    요약하면, CORS 정책은 서버 간 통신에서는 아무런 문제가 되지 않는다. 또한 CORS 문제가 일어나더라도 해당 문제는 브라우저가 서버에서 정보를 받고도 CORS 정책에 위배되면 해당 정보를 버리면서 발생하는 문제이기 때문에, 백엔드 서버에서는 브라우저에 정보를 성공적으로 보냈다고 로그가 뜬다.

    이러한 이유로 CORS 에러는 브라우저 내에서 확인해야 한다.


    CORS 정책 해결하기

    Access-Control-Allow-Origin 헤더 설정

    CORS 정책 위반 문제를 해결하기 위한 가장 정석적인 방법은, 서버에 Access-Control-Allow-Origin 헤더 설정을 해주는 것이다.

    app.get("/", (req, res) => {
      res.header("Access-Control-Allow-Origin", "*").status(200).send(req);
    }

    헤더 값으로 와일드 문자인 "*"를 사용한다면, 모든 Origin에서의 리소스 요청을 허용한다.

    하지만, 모든 Origin에서의 요청을 허용한다면, 사실상 보안을 위해서 막아둔 의미가 없어진다고 볼 수 있다.

     

    따라서 다음과 같이 특정 출처에서만 요청을 허용하도록 설정하는 것이 좋다.

    app.get("/", (req, res) => {
      res.header("Access-Control-Allow-Origin", "https://pokycookie.com").status(200).send(req);
    }

    만약 여러 개의 Origin을 허용하려면, 따로 배열을 만든 다음 req로 들어온 값이 배열에 포함되어있는지를 확인해 헤더 값으로 다시 넣어주는 방식을 사용하면 된다.

    cors 라이브러리 사용 (Node JS의 경우)

    Node JS의 Express를 사용하는 경우, cors 라이브러리를 이용하면 해당 헤더 문제를 쉽게 해결할 수 있다.

    npm i cors
    const cors = require("cors");
    
    app.use(cors());

    이렇게 app.use를 이용해 cors를 전역 미들웨어의 형태로 사용하면, 모든 요청에 대해서 Access-Control-Allow-Origin 헤더 설정이 자동으로 된다. 이때 위와 같이 cors에 아무런 설정을 하지 않는다면, 모든 곳에서의 요청을 다 허용하게 된다.

     

    즉, 보안적으로는 좋지 않다.

    따라서 다음과 같이 특별히 origin을 지정하는 것이 좋다.

    const cors = require("cors");
    
    app.use(
      cors({
        origin: "http://localhost:3000",
        credentials: true,
      })
    );

    위의 코드에서는 예시를 위해 React의 개발 서버인 localhost:3000을 사용했다.

    하지만, 실제 배포 단계에서는 localhost 역시 보안상 코드에 넣어두지 않는 것이 좋다.

    Proxy (Create React App)

    Proxy
    프록시란, 서버와 클라이언트 사이에서 중계기로써 통신을 대리로 수행하는 것을 의미한다.

    보통 CORS 보안정책에 가장 많이 걸리는 경우는 로컬에서 React와 같은 개별적인 프론트엔드 개발서버와 백엔드 서버를 연결하려고 할 때이다. React의 개발 포트는 3000번이지만, Node JS는 다른 포트번호를 사용하기 때문이다.

     

    물론, 실제 배포 단계에서는 프론트엔드 개발서버를 사용하지 않고, build과정을 거친 정적 파일을 사용하여 하나의 백엔드 서버에서 서비스를 진행하기 때문에 이러한 CORS 문제가 잘 일어나지 .

     

    하지만 개발 단계에서는 임시로 이러한 문제를 해결해야 하기 때문에 프록시를 사용한다. 프록시란 서버와 클라이언트 사이에서 중계기의 역할을 하는 기능으로, React의 요청이 바로 백엔드로 가지 않고 프록시를 거쳐 간다.

    이때 해당 프록시 설정 값을 백엔드 서버 주소로 해두면, 같은 Origin에서 리소스 요청이 일어났다고 브라우저가 판단하기 때문에 CORS 보안 정책에 위배되지 않는다.

     

    따라서 React 개발서버에서 개발을 진행할 때 자체적으로 제공하는 프록시기능을 이용하면, 간단하게 CORS 정책을 우회할 수 있다. (Create React App을 사용하는 경우)

    "proxy": "http://localhost:4000",

    위와 같이 package.json에 proxy 설정을 한 줄 추가하면 끝이다.

    이때 프록시 설정 값은 로컬에서 개발에 사용 중인 백엔드 서버의 주소를 작성한다.

    댓글