9월13일 월요일 TIL

김병훈·2021년 9월 13일
0

til

목록 보기
80/89

cors

왜 등장하게 되었는가

예전에는 서버에서 클라이언트라고하는 파일을 가지고 있고 유저의 요청에 의해서 유저가 서버에 요청을 하면 서버에 있던 클라이언트를 유저가 받아와서 그 클라이언트에서 서버와 통신을 하거나 혹은 그 클라이언트에 static하게 담겨져있던 데이터를 유저는 일방적으로 보는 방식으로 진행을 했기 떄문에, 서버에서 내려준 클라이언트는 서버에 뭔가 위해가되는 행동을 하지 않을 것이다 라는 것으로 의심의 여지가 없을 것이기 때문에 왜냐하면 origin이라는 것이 서버에서 나와서 그 서버의 것으로 다시 서버에 요청하는 것이기 때문에, origin 같아서 즉 same origin 이기 때문에 굳이 서버는 이 요청을 막을 이유가 없었다. 그래서 그대로 통신을 하게 진행했다. 그런데 최근에들어서 single page application이라고 하는 것이 등장했고, 웹 어플리케이션이 점점 더 고도화 되면서 우리 서버에만 요청하는 것이 아니라 이전에 했던 스프린트에서 혹은 유튜브 api를 활용한다던지 등 우리가 만든 어플리케이션에서 다른 서버에 있는 리소스를 필요로 하는 복잡한 웹 앱 어플리케이션이 많아지게 되면서 우리가 클라이언트에서 받아왔던 그 클라이언트에서 같은 오리진으로 리소스를 요청하는 것은 이전에도 가능했는데 , 이제는 same origin 이 아니라 다른 origin으로 요청을 해야한다. 이것을 cross origin 요청 이라고 하고, cors가 사실 cross origin resource sharing이라고 한다. 그 뜻은 같은 오리진이 아닌 다른 오리진에서 예를들어 깃헙에서 유튜브로 그런 방식의 다른 서버에 있는 리소스 자원을 요청해서 사용하려고 cross origin 요청이라는 것이 필요하게 되었다 . 그러나 mdn에서 찾아보면 보안상의 이유로 브라우저들은 cross origin 요청을 제한하고 있다. 왜냐하면 내가 서버에서 내려준 클라이언트가 아닌데 우리서버에 어떤 요청을 할지 .. 만약 요청을 열어놓으면 서버에 어떤 리소스를 생성할지를 확인할 수 없기 때문에 보안상의 이유로 cross origin 요청은 애초에 막혀있었다. 하지만 개발자들은 이런 웹 앱 어플리케이션을 조금 더 고도화 하기 위해서 cross-domain 요청을 할 수 있도록 브라우저 회사들에게 요청했고, 그래서 서버가 허용한 범위내에서는 cross origin 요청을 하게 끔 변경이 되면서 이제 스프린트를 진행할 때

이런 defaultCorsHeaders 라고 되어있는 코드를 볼 수 있다.
이 코드는 첫번째: 어떤 오리진을 액세스 할 수 있게 허용해줄거냐 -> *(모든오리진에서 가능) 모든 도메인에서 우리 서버로 cross-domain 요청을 할 수 있고 그때의 메소드는 get,post,put,delete,options 만 허용한다 . 그리고 헤더에는 content-type과 accept만 쓸 수 있고, preflight 요청은 10초까지 허용이 된다라고 하는 것이다.
지금 보는게 이러한 조건들을 갖춘 요청들은 cross-domain에서 리소스 요청이 가능하도록 허용해주겠다는 설정 코드이다.
그래서 만약에 어떤 서버가 naver.com 이라고 하는 오리진에만 cross-domain 요청을 허용해줬다면, naver.com은 처음에 options라는 preflight request를 통해서 서버에 cors origin에 대한 request 정보를 확인한 후 그 다음에 브라우저가 자동으로 options라는 요청을 서버로 보내서 cross-origin 요청에 대한 허가를 받은 다음에 진짜로 post요청을 하는 방식으로 cross-origin 요청을 진행을 하게 된다.
그래서 options를 간단하게 요약한다면, preflight request 이다. 브라우저에서 자동으로 어떤 클라이언트가 어떤 요청을 어떤 다른 origin에 있는 서버에 요청을 하고자 request를 할 때 실제로 naver 클라이언트는 서버에 post요청으로 어떠한 헤더조건을 갖춘 post요청을 하고 싶어서 그 요청을 하게 되면 그때 브라우저가 먼저 options라는 메소드로 서버로 가서 나는 이러한 방식으로 post 요청을 할 건데 이거는 너희 서버가 허용하고 있니 라고 물어보고 지금 서버는 naver.com 이라는 origin을 허용을 하고 있으니까 우리서버에 리소스 요청을 할 수있어 라고 다시 options 에 대해서 정상응답을 보내주고 그러면 실제로 우리가 클라이언트에 설정했던 post request를 서버로 보내서 리소스를 생성하게되는 방식으로 cross-origin 요청을 하게 되는데, 지금 서버는 naver.com 이라고 하는 것에만 origin을 허용하고 있다고 가정하면 익명의.com 에서 서버로 리소스 요청을 했을 때 origin 명단이 없기 때문에 우리서버로 요청을 할 수 없다는 거절 응답을 보내면 서버로 리소스 요청을 할 수 없게 된다. options 는 간략하게 preflight request라고 했는데 , mdn문서를 참고해서 이 메소드가 어떠한 의도를 가지고 있는지 cors mdn 문서를 확인해보자.

miniNodeServer

const http = require('http');

const server = http.createServer((request, response) => {
  const { method, url, headers } = request;

  // 작업진행

  if (method === 'POST' && url === '/lower') {
    let body = [];
    request
      .on('data', chunk => {
        body.push(chunk);
      })
      .on('end', () => {
        body = Buffer.concat(body).toString();

        console.log('너는 POST /lower를 요청했구나');

        response.writeHead(200, corsHeader);
        response.end(body.toLowerCase());
        return;
      });
  } else if (method === 'POST' && url === '/upper') {
    let body = [];
    request
      .on('data', chunk => {
        body.push(chunk);
      })
      .on('end', () => {
        body = Buffer.concat(body).toString();

        console.log('너는 POST /upper를 요청했구나');

        response.writeHead(200, corsHeader);
        response.end(body.toUpperCase());
        return;
      });
  } else if (method === 'OPTIONS') {
    console.log('너는 preflight request를 보냈구나');
    response.writeHead(200, corsHeader);
    response.end();
  } else {
    console.log(method, url);
    console.log('너는 다른 요청을 보냈구나');
  }
});

server.listen(5000);

let corsHeader = {
  'access-control-allow-origin': '*',
  'access-control-allow-methods': 'POST',
  'access-control-allow-headers': 'Content-Type',
  'access-control-allow-max-age': 86400,
};
  • 틀린 코드
const express = require('express');
const bodyParser = require('body-parser');
const jsonParser = bodyParser.text();
const cors = require('cors');
const PORT = 5500;  

const myLogger = (req, res, next) => {
  console.log(`http request method is ${req.method}, url is ${req.url} `);
  next();
};

const app = express();
app.use(cors());
app.use(myLogger);
app.use((req, res, next) => {
  // 토큰 있니? 없으면 받아줄 수 없어!
  if (req.headers.token) {
    req.isLoggedIn = true;
    next();
  } else {
    res.status(400).send('invalid user');
  }
});

app.post('/upper', jsonParser, (req, res) => {
  res.status(200).send(req.body.toUpperCase());
});

app.post('/lower', jsonParser, (req, res) => {
  res.status(200).send(req.body.toLowerCase());
});

app.get('/', jsonParser, (req, res) => {
  res.send('HELLO');
});

app.listen(PORT, () => {
  console.log(`Server is listening : http://localhost:${PORT}`);
});
profile
블록체인 개발자의 꿈을 위하여

0개의 댓글