ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Express.js] CORS(Cross-Origin Resource Sharing)
    Back-End/Express.js 2021. 2. 13. 00:46
    반응형

    😵 서론

    2차 프로젝트 중반부, 로그인/회원가입 모달창도 만들고 Express도 어느정도 공부해서 로컬서버를 구축했다.

    드디어, 프론트와 백을 연결한다는 기대감에 차있던 순간! 아래와 같은 에러창과의 첫만남을 가졌다~

     

    🚨 Access to fetch at ‘http://localhost:5000/users/signin’ from origin ‘http://localhost:3000’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.

     

    멘탈을 부여잡고 천천히 읽어보니, CORS 정책을 위반했고... Access-Control-Allow-Origin를 Request 헤더에 추가하라는 내용인 것 같았다. 우선 저놈의 CORS가 뭔지부터 시작해보기로 했다!


    📗 CORS 란?

    CORS(Cross-Origin Resource Sharing), 혹은 '교차 출처 리소스 공유' 정책이라고 일컫는다.

     

    이전 SOP(Same-Origin Policy)에 따르면, 브라우저는 다른 도메인의 API를 요청할 수가 없었다.

    클라이언트가 본인과 다른 출처의 서버로 요청을 보낸 경우, 브라우저가 보안상 문제로 이를 차단한다.

    * 나의 경우엔, 로컬URL에서 클라이언트(3000번 포트) 와 서버(5000번 포트) 차이로 제한이 걸린 것이다.

     

    이를 우회하고, 다른 출처간의 통신을 가능케 하는 새로운 정책이 바로 CORS인 것이다.

    * 여기서 출처는, 도메인(프로토콜, 호스트, 포트)로 생각하면 쉽다.

    출처: https://velog.io/@hongin/%EC%9D%B8%EC%9E%90%ED%95%9C-%EA%B0%9C%EB%B0%9C%EB%A1%9C%EA%B7%B8nodeJS-CORS

     

     

    - CORS 출시 배경

    이전에는, 특정 도메인 출처에서 다른 서버로 요청을 보내지 못했다. (HTML파일, 이미지, API 등)

    다른 도메인에서의 접근을 해킹으로서 인식했기 때문이다. 하지만 오늘날, 이는 오히려 단점으로 작용했다.

     

    Open API를 활용하고, 또 비동기 통신으로 가져오면서 다른 출처간의 통신이 필수적이어진 것이다.

    그렇게, SOP 우회를 위해 서버측 설정이 필요하고, 여기에 Cross-Origin을 적용할 도메인을 요청시 보내줘야 한다.

    그리하여, 클라이언트 요청시 헤더에 Access-Control-Allow-Origin 목록을 첨부해 Cross-Origin을 가능케 하는 것이 바로 CORS 인 것이다.


    📗 CORS 시나리오

    그렇다면, 나는 CORS 정책을 통해 클라이언트와 서버 통신을 하고 싶은데!!

    위처럼 에러가 발생한 이유는, 적절한 설정을 하지 않아 불가능했던 것이다.

     

    기본적인 CORS 시나리오는 아래와 같다.

    1. HTTP 프로토콜 시, Request 헤더 Origin 항목에 클라이언트 출처를 담아 보낸다.
    2. 서버에서, Response 헤더 Access-Control-Allow-Origin 항목에 이 도메인에 접근가능한 출처 목록을 보낸다.
    3. 이후, 응답을 받은 브라우저가 이 둘을 비교해서, 유효하다면 리소스를 받는 것이다.

     

    기본적인 Flow는 위와 같지만, 공식문서에 따르면 CORS는 3가지 시나리오에 따라 가불여부를 판단한다.

     

    1. Simple Requests

    특정 3가지 조건을 만족하면, 서버는 별도처리 없이도 Access-Control-Allow-Origin: * 을 반환하며 CORS 통신을 승인한다. (이후에 다룰 Prefilght Requests 처럼 예비요청이 필요없어진다.)

     

    1. 요청 메서드는 GET, HEAD, POST 로 한정한다.
    2. POST의 경우, 커스텀 헤더를 전송하면 안된다. (Accept, Accept-Language, Content-Language, Content-type, DPR, Downlink, Save-Data, Viewport-Width, Width 외 값이 있으면 안됨)
    3. Content-Type의 경우, application/x-www-form-urlencoded, multipart/form-data, text/plain 값만 허용한다.

     

    2. Prefilght Requests

    Preflight 는 가장 보편적인 시나리오이다. (사용자 인증 Authorization, HTTP API 타입인 application/json 등)

    이 상황에서는 브라우저가 예비요청, 본요청 두 번에 걸쳐 서버에 전송한다.

    * 안전확인을 위해 보내는 예비요청을 Preflight 라고 부르며, 여기엔 HTTP의 'OPTIONS' 메서드가 사용된다.

    • OPTIONS 메서드로 예비요청을 보낸 뒤, 유효할 시 본 요청을 보내는 형식이다.
    • Origin 정보뿐 아니라, 이후에 보낼 본요청 정보도 포함된다. (Access-Control-Request-Header, Method 등)
    • Origin과 Access-Control-Allow-Origin을 브라우저가 비교해 출처가 다르면 에러발생, 같으면 본 요청을 보냄
    • Simple Requests 조건에 해당하지 않으면 브라우저는 Preflight Requests 방식을 적용한다.(Default)

     

    3. Credentialed Requests

    인증된 요청을 사용하는 방법이다. 이 시나리오는 CORS 기본적인 방식이라기 보다는 특정 출처와의 통신에서 보안을 강화하는 목적으로 사용되는 것이다.

     

    기본적으로 RESTful API 형식인 XMLHttpRequest 객체나 fetch API(JSON) 통신에서는 별도 옵션없이 브라우저의 HTTP Cookie와 HTTP Authentication 정보를 함부로 요청에 담지 않는다.

    이를 첨부하기 위한 옵션이 credentials 옵션이며, 종류는 아래와 같이 3가지가 있다.

    옵션  값설명
    same-origin (기본값) 같은 출처 간 요청에만 인증 정보를 담을 수 있다
    include 모든 요청에 인증 정보를 담을 수 있다
    omit 모든 요청에 인증 정보를 담지 않는다

     

    fetch('https://localhost:5000/users/signin', {
      credentials: 'include',
    });

    만약 서버 요청에 위처럼 include 옵션을 부가한다면, Access-Control-Allow-Origin: * 헤더를 사용할 수 없다!

    1. Response 헤더에는 반드시 Access-Control-Allow-Credentials: true 가 존재해야 한다.
    2. Access-Control-Allow-Origin 에는 *을 사용할 수 없으며, 명확한 URL 주소가 첨부되야한다.

    📗 Express 에서 CORS 해결하기

    CORS 개념은 매우 어렵기도 하고... 사실 공부해야할 양이 방대하기에 현상에 대한 이해만 하도록 하자.(나역시도)

    Node 서버에서 CORS 이슈를 해결하는 2가지 방법을 간단하게 소개하겠다.

     

    1. Response 헤더에 Access-Control-Allow-Origin 추가

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

    먼저, 서버의 리스폰스 헤더에 ("Access-Control-Allow-Origin", "[클라이언트 URL]") 을 추가하는 것이다.

    위는 모든 경로를 허용했지만, 특정 출처의 요청에만 적용하고자 할 때 적합한 방법일 것 같다.

     

    2. 미들웨어 CORS 추가

    Node.js에서 제공하는 미들웨어 cors를 활용할 수 있다.

     

    - 설치

    npm install cors --save

     

    - 적용

    // app.js
    const cors = require('cors');
    
    app.use(cors());

     

    만약, 1번과 같이 특정 URL에 한정시키고 싶다면 아래처럼 옵션을 추가할 수 있다.

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

    새삼.. 1차 프로젝트를 같이 했던 백엔드 동기에게 감사함을 느꼈다. 😳😳😳

     

    정말 기본적인 정책에 대해서도 짚고 넘어가지 못 할 뻔했다.

    더군다나, 블로그를 공부하면서 CORS는 Cross-Domain을 제재하는 것이 아니라 오히려 SOP(Single-Origin Policy)를 우회하기 위해 등장한 정책인 것을 알게 되었다! 

     

    이래서 백엔드가 좀 더 웹지식 근간에 있구나 하는 느낌도 들었고, 그래서 이번 프로젝트에서 Express를 간단하게나마 공부했던 것을(그리고 이를 추천해준 준형이에게도) 뿌듯함을 느낀 계기였다.

     

    [출처]

    - CORS MDN 공식 사이트: developer.mozilla.org/ko/docs/Web/HTTP/CORS  

    - Evan-Moon 님의 블로그: evan-moon.github.io/2020/05/21/about-cors/  

    - 홍인자 님의 블로그: velog.io/@hongin/%EC%9D%B8%EC%9E%90%ED%95%9C-%EA%B0%9C%EB%B0%9C%EB%A1%9C%EA%B7%B8nodeJS-CORS

    반응형
Designed by Tistory.