본문 바로가기

개념/기초지식

[HTTP/네트워크] CORS, SOP

코드스테이츠 교육엔지니어님 말로는 기술면접에 나올 수 있는 내용이라고 한다.
꼭 말로 설명할 수 있도록 머리에 담아놓자!

 

🧐 SOP이란

 

SOP(Same-Origin Policy)이란 동일 출처 정책을 뜻한다.

쉽게 풀어 말하자면 ‘같은 출처의 리소스만 공유가 가능하다’라는 정책이다.

여기서 말하는 ‘출처(Origin)’는 다음과 같이 이루어져있다.

출처 = 프로토콜 + 포트 포함 호스트

 

https://todayscommit.tistory.com:443/에서
https://는 프로토콜
:443은 포트
todayscommit.tistory.com:443는 호스트를 말한다.
(자주쓰이는 포트는 생략가능해서 안보임)

 

출처는 프로토콜, 호스트(포트포함)의 조합으로 되어있다. 이 중 하나라도 다르면 동일한 출처로 보지 않는다.

반면 뒤에 /course,말고 /hi 가 와도, 출처 뒤에 라우팅만 다르게 해준 것이기 때문에 동일출처로 본다.

 


👀 SOP, 왜 필요할까

 

SOP(동일 출처 정책)은 잠재적으로 해로울 수 있는 문서를 분리함으로써 공격받을 수 있는 경로를 줄여준다.

SOP을 통해 해킹 등의 위협에서 보다 더 안전해질 수 있다.

SOP은 애초에 다른 사이트와의 리소스 공유를 제한하기 때문에 로그인 정보가 타 사이트의 코드에 의해서 새어나가는 것을 방지할 수 있다. 이러한 보안상 이점 때문에 SOP은 모든 브라우저에서 기본적으로 사용하고 있는 정책이며, 꼭 알아둬야 한다.

 


☄️ SOP의 한계를 보완하는 CORS

 

그런데, 다른 출처의 리소스를 사용하게 될 일은 너무나도 많다. 이말은 SOP정책을 어겨야 하는 상황이 너무나 많다는 뜻!

 

당장 로컬 환경에서 개발을 할 때에도 클라이언트와 서버를 따로 개발하게 된다면 둘은 출처가 달라지게 된다.

아래와 같은 상황들이 모두 SOP을 어겨야하는 상황이다.

  • 개발중인 웹 사이트에서 네이버 지도 api를 사용하고 싶다
  • github 정보를 받아와서 사용하고 싶다
  • 기상청 API를 사용해서 오늘의 날씨를 보여주고 싶다.

위는 모두 다른 출처의 리소스를 사용해야하는 일이다.

하지만, 말했듯 모든 브라우저는 기본적으로 SOP 정책을 사용하고 있다.

그렇다면 어떻게하면 다른 출처의 리소스를 받아올 수 있을까? 

 

이런 문제 상황에서 필요한 것이 바로 CORS 이다.


🧐 CORS 란

 

CORS(Cross-Origin Resource Sharing)의 줄임말로 교차 출처 리소스 공유를 뜻한다. 

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

 

따라서 다행이도 우리는 다른 출처의 리소스를 가져오려고 했지만 SOP 때문에 접근이 불가능하다면, CORS 설정을 통해 서버의 응답 헤더에 ‘Access-Control-Allow-Origin’을 작성하면 접근 권한을 얻을 수 있다.

 

한마디로 CORS는 SOP으로 인한 에러를 해결하기 위한 방법인것이다.


🦾 CORS 동작 방식의 세가지 동작방식

  1. 프리플라이트 요청 (Preflight Request)
  2. 단순요청 (Simple request)
  3. 인증정보를 포함한 요청 (Credentialed Request)

위의 세가지는 아래에서 더 자세히 설명한다.


🧚🏻 CORS 동작 방식1: 프리플라이트 요청 (Preflight Request)

 

프리플라이트 요청이란 실제 요청을 보내기 전, OPTIONS 메서드로 사전 요청을 보내 해당 출처 리소스에 접근 권한이 있는지부터 확인하는 것을 말한다.

 브라우저는 서버에 실제 요청을 보내기 전에 프리플라이트 요청을 보내고,

응답 헤더의 Access-Control-Allow-Origin으로 요청을 보낸 출처가 돌아오면 그때 비로소 실제 요청을 보내게 된다.

 

⁉️ 프리플라이트 요청 (Preflight Request) 왜 필요할까

  • 실제 요청을 보내기 전에 미리 권한 확인을 할 수 있기 때문에, 실제 요청을 처음부터 통째로 보내는 것보다 리소스 측면에서 효율적이다.
  • CORS에 대비가 되어있지 않은 서버를 보호할 수 있다. CORS 이전에 만들어진 서버들은 SOP 요청만 들어오는 상황을 고려하고 만들어졌다. 따라서 다른 출처에서 들어오는 요청에 대한 대비가 되어있지 않았다.

 

http 서버로 cors 프리플라이트  예시

const http = require('http');

const PORT = 4999;

const ip = 'localhost';

const server = http.createServer((request, response) => {
  const { method, url } = request;
  let body = [];
  if(method === 'OPTIONS' ){ //📌프리플라이트 옵션은 OPTIONS 메소드로 들어온다.
    // CORS 설정을 돌려줘야 한다
    response.writeHead(200, defaultCorsHeader); //💡 모든 응답에 밑에서 작성한 cors 헤더붙이기
    response.end()
  }

  if(method === 'POST' && url === '/upper' ){
    //대문자를 응답을 돌려줘야한다
    request.on('data', (chunk) => {
      console.log(chunk)
      body.push(chunk);
    }).on('end', () => {
      body = Buffer.concat(body).toString().toUpperCase();
      response.writeHead(200, defaultCorsHeader); //💡 모든 응답에 밑에서 작성한 cors 헤더붙이기
      response.end(body)
    });
  }
  else if(method === 'POST' && url === '/lower' ){
    //소문자를 응답을 돌려줘야한다
    request.on('data', (chunk) => {
      body.push(chunk);
    }).on('end', () => {
      body = Buffer.concat(body).toString().toLowerCase();
      response.writeHead(200, defaultCorsHeader); //💡 모든 응답에 밑에서 작성한 cors 헤더붙이기
      response.end(body)
    });
  }
  else{
    //에러로 처리해준다. bad request
    response.statusCode = 404;
    response.end();
  }

});

server.listen(PORT, ip, () => {
  console.log(`http server listen on ${ip}:${PORT}`);
});

const defaultCorsHeader = { //💡 여기서 CORS처리
  'Access-Control-Allow-Origin': '*', //모든 출처의 접근을 허용한다.
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Accept',
  'Access-Control-Max-Age': 10
};

 


🧚🏻 CORS 동작 방식2: 단순 요청(Simple Request)

 

단순요청이란 특정 조건이 만족되면 프리플라이트 요청을 생략하고 요청을 보내는 것을 말한다.

단순요청은 아래와 같은 특징이 있다.

  • GET, HEAD, POST 요청 중 하나여야 한다.
  • 자동으로 설정되는 헤더 외에, Accept, Accept-Language, Content-Language, Content-Type 헤더의 값만 수동으로 설정할 수 있다.
    • Content-Type 헤더에는 application/x-www-form-urlencoded, multipart/form-data, text/plain 값만 허용된다.

🧚🏻 CORS 동작 방식3: 인증정보를 포함한 요청 (Credentialed Request)

 

credentialed request란 쿠키가 필요할 때 요청 헤더에 인증 정보를 담아 보내는 요청이다.

 

출처가 다를 경우에는 별도의 설정을 하지 않으면 쿠키를 보낼 수 없다.

민감한 정보이기 때문!! 이 경우에는 프론트, 서버 양측 모두 CORS 설정이 필요하다.

  • 프론트 측에서는 요청 헤더에 withCredentials : true 를 넣어줘야 한다.
  • 서버 측에서는 응답 헤더에 Access-Control-Allow-Credentials : true 를 넣어줘야 한다.
  • 서버 측에서 Access-Control-Allow-Origin 을 설정할 때, 모든 출처를 허용한다는 뜻의 와일드카드(*)로 설정하면 에러가 발생한다. 인증 정보를 다루는 만큼 출처를 정확하게 설정해주어야 한다.

 

프리 플라이트 🧚🏻