데굴데굴

TIL: 2022-10-13 본문

Lesson/TIL

TIL: 2022-10-13

aemaaeng 2022. 10. 13. 23:04

⚙️ 오늘 배운 주제

Node.js로 서버 만들기 (+CORS 에러 다루기)

 

🐹 오늘의 기분

오늘 학습도 만만치 않았다. SOP과 CORS 개념 볼 때까지만 해도 음~ 그렇군~ 이 상태였는데 실습 넘어오면서부터 머리에 물음표가 가득차기 시작했다. 당최 무슨 소린지 모르겠어서 같은 영상을 한 다섯 번은 본 것 같다. node.js의 http 모듈 관련 공식 문서를 참고해가며 하는 실습이었는데 거기도 한 번에 이해되지는 않아서 몇 번이고 다시 읽었다. 일단 예제에 있는대로 써보면서 이해해보려 했으나... 쉽지 않았다. CORS 에러를 처리하는 코드를 아직 이해 못했다. 분명 잘 분기해줬다고 생각했는데 자꾸 오류가 났음. 이 부분은 더 깊게 공부를 해보고 글에 추가해봐야겠다.

 

🗝 키워드

http 모듈, SOP, CORS, 프리플라이트 요청, 단순 요청, 인증 요청

 

🗣 스스로에게 설명

SOP

Same-Origin Policy

같은 출처(origin)의 리소스만 공유가 가능하다는 원칙이다.

 

origin은 fetch가 시작하는 위치로, 경로는 제외하고 서버 이름만 포함한다. 

Origin: <scheme> "://" <hostname> [ ":" <port> ]

아래 이미지를 보면 <scheme>과 <hostname>, <port>가 어떤 부분인지 쉽게 알 수 있다.

path 직전까지를 origin으로 보면 된다. (port는 생략 가능)

origin은 개발자 도구에서 확인할 수 있다. (location 객체의 origin을 출력)

내 블로그에서는 이렇게 나온다.

 

SOP 정책에 따라 요청을 보내는 곳의 origin요청을 받는 곳의 origin이 같아야 리소스를 공유할 수 있다.

origin 같은 것, 다른 것 예시 (포트번호까지)

origin의 일치 여부는 브라우저가 판단한다고 한다. 

 

SOP는 왜 필요한가?

만약 다른 리소스에서 정보를 자유롭게 접근할 수 있다면 정보가 유출될 위험이 높아진다.

특히 웹은 개발자 도구를 통해 어떤 작업이 이루어지고 있는지를 대부분 열람할 수 있기에 타 리소스의 접근 차단이 더욱 요구된다.

SOP로 다른 리소스와의 정보 공유를 제한함으로써 보안을 높일 수가 있다. 

이 원칙이 있기에 우리는 안전한 브라우징을 할 수 있다.

 

SOP는 다른 리소스와의 정보 공유를 제한한다고 했는데, 그럼 다른 리소스가 필요한 경우에는 어떻게 해야 할까?

 

CORS

Cross-Origin Resource Sharing

이 때 필요한 것이 CORS이다. 

CORS는 말 그대로 origin이 다른 리소스를 공유하는 것이다.

mdn에서는 아래와 같이 설명하고 있다.

추가 HTTP 헤더를 사용하여 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한 부여

여기서 추가 HTTP 헤더는 Access-Control-Allow-Origin을 말한다.

 

CORS 동작 방식

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

Preflight 요청이란 서버에 영향을 미칠 수 있는 메소드(POST, PUT, DELETE 등)를 쓰기 전에 해당 서버가 이 요청의 접근 권한을 허용하고 있는지 OPTIONS 메소드를 이용한 사전 요청으로 미리 확인해보는 과정이다. 

A preflight request is automatically issued by a browser

mdn을 보면 'Preflight 요청은 브라우저가 자동으로 보낸다'고 나와있다. ( 🔗 )

따라서 우리는 응답 메시지를 다뤄주면 된다.

 

브라우저의 네트워크 탭을 보면 요청들을 볼 수 있다.

POST /lower을 보냈을 때의 결과다.

POST 요청만 보냈음에도 불구하고 브라우저가 보낸 preflight 요청이 하나 더 생겨있다.

네트워크 탭 ALL

Preflight 옆의 동그란 화살표를 누르면 어떤 요청에 대한 preflight 요청인지 노란색 하이라이트로 표시해준다. 

Preflight 요청을 클릭하여 Request 메시지와 Response 메시지가 어떻게 오고 갔는지 확인할 수 있다.

 

Request Message

OPTIONS /lower HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: POST
Connection: keep-alive
Host: localhost:4999
Origin: null
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site

Access-Control-Request-Method : 접근 허가를 얻을 메소드 

 

Response Message

HTTP/1.1 200 OK
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
Date: Thu, 13 Oct 2022 07:44:53 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Transfer-Encoding: chunked

Access-Control-Allow-Origin : 공유를 허용할 리소스 origin

-> 위 메시지에서는 '*'로 입력되어있는데 이는 모든 리소스를 허용한다는 뜻이다. 

실습이라 이렇게 썼지만, 실제로는 저렇게 쓰면 안 되고 꼭 공유를 허용할 리소스의 origin만 적어주어야 한다! 

Access-Control-Allow-Methods : 접근을 허용할 메소드들

위에서는 GET, POST, PUT, DELETE, OPTIONS를 허용하고 있다.

Access-Control-Allow-Header : 접근을 허용할 헤더

Access-Control-Max-Age : 요청을 캐시해둘 시간(초)

 

왜 프리플라이트 요청을 보내야 하는가?

  • 미리 권한 확인 -> 처음부터 모든 요청을 보내는 것보다 리소스 절약 가능
  • CORS에 대비되어 있지 않은 서버 보호 가능

2. 단순 요청 (Simple Request)

특정 조건이 만족되면 Preflight 요청을 생략하고 바로 실제 요청을 보낸다.

하지만 이 조건이 매우 복잡해서 거의 모든 요청이 Preflight 요청을 보낸다고 봐도 된다고 한다.

조건의 자세한 내용은 mdn 문서에 잘 나와있다. (접근 제어 시나리오 부분)

 

교차 출처 리소스 공유 (CORS) - HTTP | MDN

교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라

developer.mozilla.org

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

요청 헤더에 인증 정보를 담아 보내는 요청이다.

요청을 보낼 때 별도의 설정을 하지 않으면 쿠키를 같이 보낼 수 없다. 

인증 정보 요청을 다룰 때에는 프론트와 백 전부 CORS 설정을 해야 한다. 

프론트에서는 fetch 사용 시 withCredentials : true를 설정하고,

서버에서는 Access-Control-Allow-Credentials를 true로 설정하면 인증 정보를 포함한 요청도 받을 수 있다.

(이 부분은 직접 써보지 않아서 잘 모르겠다... )

 

CORS에 대해 설명이 굉장히 잘 된 글이 있어서 올려둔다. 

 

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

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

evan-moon.github.io

 

Node.js http 모듈로 서버 만들기

Node.js 공식 문서의 내용을 참고하여 정리했다.

 

HTTP 트랜잭션 해부 | Node.js

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.

nodejs.org

require로 http 모듈을 불러온다.

const http = require("http");

서버의 포트와 ip를 지정하고, createServer로 서버를 만들어 server 변수에 할당한다.

createServer로 서버를 만들면 request 객체와 response 객체가 생성된다.

const PORT = 4999;
const ip = "localhost";

const server = http.createServer((request, response) => {
  // 여기에 서버 동작 내용을 구현함
}

서버가 생성되었으면 서버로 들어온 요청을 처리해야 한다.

request 객체에 method, url 프로퍼티가 내장되어 있어 이를 이용해 분기하면 된다.

GET /user 요청이 들어왔을 경우를 가정해봤다.

const server = http.createServer((request, response) => {
  // 여기에 서버 동작 내용을 구현함
  if (request.method === "GET" && request.url === "/user") {
    // 동작 내용
  }
}

이렇게 요청별로 하나하나 if/else문으로 구현해줘야 한다.

 

POST나 PUT 요청에 들어온 body를 가공하려면 조금 더 복잡해진다.

POST /user 요청이 들어온 경우로 가정해보겠다.

const server = http.createServer((request, response) => {
  // 여기에 서버 동작 내용을 구현함
  if (request.method === "GET" && request.url === "/user") {
    // 동작 내용
  } else if (request.method === "POST" && request.url === "/user") {
    let body = [];
    request.on('data', (chunk) => {
      body.push(chunk);
    }).on('end', () => {
      body = Buffer.concat(body).toString();
      // 변수 body에 request에 들어온 body의 내용이 문자열로 담겨 있다
    });
  }
}

 

Node.js로 CORS 에러 처리하기

브라우저가 preflight 요청을 보냈을 때 응답으로 보내줄 헤더를 작성한다.

const defaultCorsHeader = {
  "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,
};

응답 객체의 writeHead 메소드로 상태 코드와 헤더를 넣어준다. 

response.writeHead(200, defaultCorsHeader);

Preflight 요청은 options 메소드로 들어오므로 분기해준다.

const server = http.createServer((request, response) => {
  // 여기에 서버 동작 내용을 구현함
  if (request.method === "OPTIONS") {
    response.writeHead(200, defaultCorsHeader);
    response.end();
  }
  response.writeHead(200, defaultCorsHeader);
}

실습할 때 어째서인지 밑에 response.writeHead(200, defaultCorsHeader) 이 코드를 한 번 더 써줘야 cors 에러가 발생하지 않았다. 아직 이 부분은 설명을 듣지 못해서 이유는 잘 모르겠다... 애초에 내가 코드를 잘못 짰을 수도..?

이유를 알게 되면 추가해야겠다. 

 

찾아보니 cors 모듈이 따로 있다고 한다.

그걸 이용하면 더 편할 듯하다.

 

[NODE] 📚 cors 모듈 - CORS 간편 설정하기

CORS 허용 설정 하는 방법 Node.js 서버 프로젝트에서 cors(cross origin resource sharing) 문제를 해결하는 방법은 크게 2가지가 있다. 하나는 직접 헤더를 명시해서 출처(origin)을 필터링하는 것이고, 다른..

inpa.tistory.com

 

❓ 막히는 or 막혔던 부분

CORS 에러 처리

 

🔍 공부가 더 필요한 부분

readableStream, writableStream

http 헤더 (content-type 등)

'Lesson > TIL' 카테고리의 다른 글

TIL: 2022-10-17  (0) 2022.10.19
TIL: 2022-10-14  (0) 2022.10.14
TIL: 2022-10-12  (0) 2022.10.12
TIL: 2022-10-11  (0) 2022.10.11
TIL: 2022-10-07  (0) 2022.10.07
Comments