210607 passport.js 로그인 기능구현

Passport.js 로그인 인증

Passport.js를 활용한 로그인 흐름

아래는 passport-local을 사용한 일반 로그인 과정의 예시이다.

Front-End
(1) 로그인 폼에 이메일과 비밀번호를 입력
(2) saga의 로그인 함수 실행

Back-End
(3) login POST router 실행
(4) passport.authenticate(‘local’, …) 부분이 실행
(5) passport/local.js 로그인 전략 부분이 실행
(6) (5)의 결과가 passport.authenticate(‘local’, …) 부분의 callback 함수로 전달되고, 로그인에 문제가 없는 경우, passport 로그인 시도(req.login)
(7) passport/index.js의 serializeUser부분이 실행된다.
serializeUser부분에서는 cookie에 묶어 줄 로그인 사용자 id를 전달한다.

deserializeUser부분은 로그인이 성공한 이후부터의 요청부터 router가 실행되기 이전에 실행된다.
deserializeUser부분이 실행되면, 저장되었던 사용자의 id를 토대로 사용자 정보를 복구해서 done(null, user)를 통해 req.user로 사용자 정보를 전달한다.

이를통해 router에서는 req.user로 사용자 정보의 참조가 가능해진다.

(8) 내부적으로 header를 통해 Front-End로 전달할 cookie 정보를 전달한다.
(9) Cookie 정보와 함께 Front-End로 사용자 정보를 전달한다.

노트필기 참고

Session & Cookie, Login 노트필기

Read More

210605 cookie, session, JWT(JSON Web Token)

Session & Cookie

cookie와 session이 필요한 이유

cookie와 session이 필요한 이유를 이해하기 위해서는 HTTP 프로토콜의 connectionless, stateless한 특성에 대한 이해가 필요하다.
connectionless는 클라이언트에서 요청을 한 뒤에 서버로부터 응답을 받으면 해당 연결을 끊어버리는 HTTP 프로토콜의 특징이다. HTTP가 TCP를 기반으로 구현되었기 때문에 네트워크 관점에서 keep-alive는 옵션으로, 연결비용을 줄이는 것을 장점으로 비연결지향이라고 한다.

stateless는 클라이언트와 서버의 통신이 끝나면 상태를 유지하지 않는 HTTP 프로토콜의 특징이다. 연결이 끊기는 순간 클라이언트와 서버간의 통신이 끝나고, 상태 정보는 유지하지 않는 것이 특징이다.

cookie와 session은 앞서 설명한 HTTP 프로토콜의 두 가지 특징(connectionless, stateless)이 가지는 단점을 해결하기 위해 사용된다.
우리가 특정 웹 페이지를 이용할때 한 번의 로그인을 한 뒤에 로그인한 사용자에 대한 인증을 유지하는 것이 바로 쿠키와 세션을 사용했기 때문이다. 쿠키와 세션을 사용하지 않는다면, 새로운 페이지로 이동할때마다 다시 로그인을 해야한다.

cookie와 session의 차이점에 있어, 정보가 저장되는 위치와 보안에 대한 부분도 있지만 이보다 더 중요한 것은 lifecycle에 대해 이해하는 것이 중요하다. cookie와 session 모두 만료시간이 있지만, cookie의 경우에는 클라이언트의 로컬에 파일로 저장되기 때문에 브라우저가 종료되어도 계속 정보가 남아있지만, 세션의 경우에는 만료시간과 상관없이 브라우저가 종료되면 삭제된다는 특징을 가지고 있다.

Read More

210605 CORS 문제해결

CORS

CORS(Cross-Origin Resource Sharing) ?

브라우저상에서 정보를 받는 곳과 보내는 곳이 신뢰할 수 있는 곳인지 확인하는 절차가 필요하다.
발신자와 수신자를 신뢰할 때 정보의 신뢰성과 보안이 보장될 수 있기 때문이다.

CORS issue는 브라우저와 서버 간의 요청에서만 발생되며, 서버와 서버간에는 발생하지 않는다.
요청을 보내는 쪽과 받는 쪽의 도메인이 서로 다른 경우, browser가 해당 요청을 차단한다.

동일 출처 정책(same origin policy)

하나의 근원지로부터 문서 또는 스크립트를 로드함으로써 다른 근원지로부터 오는 잠재적인 악성 문서를 격리하는 정책이다.

CORS 해결방법

직접적으로 사용자들의 browser를 변조해서 CORS 문제를 해결할 수 없기 때문에 아래의 두 가지 방법으로 CORS 문제를 해결할 수 있다.

  • 해결방법1: Proxy 방식

    예를들어 브라우저에서 다른 도메인인 백엔드 서버로 요청을 보내는 것은 문제가 되기 때문에 브라우저와 같은 도메인인 프론트엔드 서버를 거쳐서 백엔드 서버로의 요청과 응답을 처리하는 방법이다.

  • 해결방법2: Access-Control-Allow-Origin header 허용

    header의 Access-Control-Allow-Origin의 통해 요청이 오는 특정 도메인을 허용할 수 있다.

    예를들어 Express server에서 router의 callback 함수에서 response를 통해 header의 Access-Control-Allow-Origin 속성을 통해 특정 도메인을 허용해주거나, cors middleware를 사용해서 해결할 수 있다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    res.setHeader('Access-Control-Allow-Origin', '*');

    // cors middleware
    app.use(
    cors({
    // 요청을 보낸 도메인이 자동으로 허용된다.
    origin: true,
    credentials: false
    })
    );

210603 Server scaling

Server scaling

프론트엔드와 백엔드 서버의 구성

프론트엔드와 백엔드 서버를 각 각의 서버 PC로 구성하는 이유에 대해서 공부를 하던 중에 서버 스케일링(Server scaling)의 개념에 대해서 알게 되었다.

대규모 트래픽이 발생하는 웹 어플리케이션의 개발을 한다고 가정했을때, 프론트엔드와 백엔드 서버를 하나의 PC에 같이 관리하는 것과 각 각의 두 PC로 분리해서 관리하는 두 가지 경우로 나눠서 생각해 볼 수 있다.

우선 하나의 서버 PC에서 프론트엔드와 백엔드 서버를 모두 관리한다고 가정해보자.
만약에 프론트엔드 서버와 백엔드 서버가 8:2비율로
트래픽이 발생한다면, 프론트엔드 서버의 확장이 필요하다.

이때 서버의 스케일링을 하게 되면, 기존 서버 PC와 똑같은 서버 PC가 복제가 된다. 하지만 실제로 트래픽이 발생하는 서버는 프론트엔드 서버이기 때문에 백엔드 서버의 복제는 불필요하다.

이러한 이유로 프론트엔드와 백엔드 서버를 각 각의 서버 PC로 분리해서 관리하는 것이 자원을 아끼며 효율적으로 관리할 수 있는 것이다.

주문/배달 어플리케이션을 예로들면, 주문과 결제에 대한 트래픽이 많이 발생하기 때문에 주문과 결제를 개별 서버로 분리해서 서버의 확장이 필요한 경우에 해당 서버만 스케일링을 통해 확장하면 된다.

수직적 확장(Vertical Scaling)과 수평적 확장(Horizontal Scaling)

수평적 확장의 경우에는 로드 밸런싱이라는 것이 있기 때문에 서버 중에 하나가 실패하면 로드 밸런서가 요청을 다른 서버로 redirect하고, 새로운 서버가 추가되면 로드 밸런서가 해당 서버로 요청을 보내기 시작한다.
이처럼 수평적 확장의 경우에는 여러 서버와 효율적으로 작동하며 서버의 수를 늘리거나 줄일때 유연성을 제공한다.

하지만 수직적 확장의 경우에는 단 하나의 거대한 서버로 관리가 되기 때문에 위와 같은 수평적 확장이 가지는 유연함이 없다.
이외에도 수직적 확장은 하드웨어의 제한이라는 단점이 있다. CPU/RAM/DISK와 같은 리소스의 추가는 가능하지만, 사용자가 많아지면 충분하지 않은 경우가 생기게 되고 이러한 이유로 대기업에서는 좀 더 유연성을 제공하는 수평적인 확장이 이상적이다.

그렇다면 수직적 확장의 장점에는 어떤 점이 있을까?
수직적 확장의 장점은 데이터의 일관성이 있다는 것이다. 이는 모든 데이터가 보관된 세스템이 하나이기 때문이다. transaction이 여러 서버에 거쳐 데이터를 전송하는 수평적 확장 방식의 서버관리는 복잡성과 유지관리의 어려움이 있지만, 수직적 확장 방식의 서버 관리는 이러한 어려움이 없다.

소규모 회사를 운영하고 사용자 및 데이터가 증가하는 폭이 작다면 수직적 확장이 이상적이며, 사용자 및 데이터가 증가하는 폭이 높다면 수평적 확장이 이상적이다.

210603 Nodejs + Sequelize + MySQL

Nodejs + MySQL

Express.js 사용의 이유

아래와 같이 Node에서 제공하는 http 모듈 패키지를 사용해서 서버를 구현할 수 있다.
하지만 체계적으로 파일을 분리해서 관리하기에는 다소 어려움이 있다.
POST 방식의 처리와 GET 방식의 처리를 하기 위해서 req.method로 조건분기 처리를 하고, 그 안에서도 req.url을 통해서 요청받은 url을 구분해야 되기 때문에 조건처리하는 부분의 코드가 길어지게 되고, 코드의 가독성도 좋지 않다.

1
2
3
4
5
6
7
8
9
10
const http = require('http');
const server = http.createServer((req, res) => {
console.log(req.url, req.method);
res.write('<h1>SERVER TEST</h1>');
res.end('<h1>TEST END</h1>');
});

server.listen(3065, () => {
console.log('Server is running');
});

이러한 이유로 인해 Node.js의 프레임워크인 Express.js를 사용해서 서버를 구현하는 것이다.

head와 body

body는 res.json 또는 res.send로 보내지는 부분이다. head는 이 body(요청에 대한 응답)에 대한 부가적인 정보를 담고 있다.
(예를들어, 응답의 날짜, 데이터 타입, 용량 등의 정보를 담고 있다.)

Swagger API Documentation

Seagger 툴을 사용해서 API를 문서화할 수 있다.

[참고] : https://swagger.io/

REST API 방식

100% REST 방식을 지켜서 작성하기 어렵다. 어느정도 협상을 통해 REST 방식으로 API를 작성하도록 한다.

  • get : 취득
  • post : 생성
  • put : 전체 수정(통째로 덮어쓰기)
  • delete : 제거
  • patch : 부분 수정
  • options : 서버에 요청에 대한 확인
  • head : header 정보 취득

MySQL8

(1) MySQL Community server 설치
(2) MySQL workbench 설치 (terminal 말고 시각화해서 내부 데이터베이스 확인)

sequelize와 mysql2 driver 설치 및 설정

1
$ npm i sequelize sequelize-cli mysql2
  • mysql2 : node.js와 mysql을 연결해주는 드라이버

  • sequelize : 직접 query를 작성해주지 않아도 javascript를 sql로 변환해주는 라이브러리

1
2
# 기본 sequelize 설정
$ npx sequelize init

Read More

210602 Infinite scrolling과 Virtualized list

infinite scroll과 virtualized list

Infinite scrolling 구현

Infinite scrolling을 구현하기 위해 스크롤이 가장 최 하단으로 이동했을때 이벤트를 발생시켜야 한다.
실제 실무에서는 이벤트 발생시점이 최하단이 아닌 하단에서 일정 px 떨어진 이전 시점에 미리 로드할 데이터를 로딩시켜 사용자가 스크롤링을 했을때 끊기는 느낌이 들지 않도록 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
useEffect(() => {
// 현재 스크롤 위치
function onScroll() {
// 스크롤된 높이를 구하는데 아래 세가지 함수가 많이 쓰인다.
console.log(
// (1) 얼마나 내렸는지
// 가장 최하단으로 스크롤을 내리면, 최 하단으로 내려간 페이지의 최 상단이 마지막 내려간 지점이 된다.
window.scrollY,

// (2) 화면에 보이는 길이(최상단에서 하단 스크롤 위까지 길이)
document.documentElement.clientHeight,

// (3) 총 길이 (스크롤의 제일 위부터 아래까지 총 길이)
// (=) window.scrollY + document.documentElement.clientHeight
document.documentElement.scrollHeight

// (1)과 (2)의 합이 (3)과 같을때 스크롤이 가장 최 하단으로 내려갔다는 것을 의미한다.
// 이 시점에 새로 로딩
);
// 끝에서 300px 위보다 더 많이 내렸을때
if (
window.scrollY + document.documentElement.clientHeight >
document.documentElement.scrollHeight - 300
) {
// loadPostLoading이 false인 경우에만 새로운 요청을 한다.
if (hasMorePosts && !loadPostLoading) {
dispatch({
type: LOAD_POST_REQUEST,
data: mainPosts[mainPosts.length - 1].id
});
}
}
}

window.addEventListener('scroll', onScroll);

// 반드시 스크롤했떤 이벤트를 해제시켜줘야 한다.
// 안그러면 메모리상에 계속 남아있다.
return () => {
window.removeEventListener('scroll', onScroll);
};
}, [hasMorePosts, mainPosts, loadPostLoading]);

throttle과 takelatest

infinite scrolling을 구현하면서 scroll이벤트가 발생할때마다 매번 비동기 요청이 발생을 하였는데, 이 문제를 해결하기 위해 throttle과 takelatest를 사용해서 해결해보려고 했다.
하지만 throttle과 takelatest 모두 요청(Async request)은 차단되지 않고, 응답(Response)만을 차단하기 때문에 만약에 5s 간격으로 throttle 처리를 하게 되면, 받은 요청에 대해서는 취소되지 않고, 5s 간격으로 지연되서 처리가 되는 것을 확인할 수 있었다.

본질적으로 해결하고자 하는 것은 scroll이벤트에서 발생되는 요청 자체를 차단하는 것이기 때문에 이 문제 해결을 위해 redux의 상태 데이터에서 해당 비동기 처리 요청에 대한 loading과 관련된 상태값을 활용하여 해결하였다.

Read More

210601 faker를 사용해서 dummy data 만들기, placeholder.com, Redux toolkit

faker

faker

개발을 하다보면 임의로 데이터를 넣어 실제 페이지가 어떻게 출력이 되는지 확인해야될 때가 있다.
소수의 데이터는 괜찮지만 10개 이상의 데이터를 넣어서 확인을 해야 될 때에는 여간 번거로운 일이 아니다.
이런 경우에 facker라는 패키지를 사용해서 임의의 dummy 데이터를 넣어 줄 수 있다.
(무한 스크롤링 테스트)

[참고]: https://www.npmjs.com/package/faker

1
$ npm i faker

Placeholder.com

dummy 이미지 대체하여 사용할 수 있다.

[참고] : https://placeholder.com/

Redux toolkit

createReducer, createAction을 사용해서 Redux를 사용할때 코드가 길어지는 것을 보완할 수 있다.

이전에 공부할때 정리한 내용을 참고하자.

http://localhost:4000/2021/04/30/202104/210430-Redux_TIL/

210526 immer

immer

immer

state의 데이터 구조가 복잡해지면 불변성을 지키기 위해 변하지 않는 값에 대해 spread 문법을 사용해서 얕은 복사를 하는 것이 지저분해질 수 있는데 immer를 사용하면 이 문제를 해결할 수 있다.

1
$ npm i immer

Redux에서만 아니라 React의 useState, setState에서도 immer를 사용할 수 있으며,hooks를 위한 useImmer도 있다.(useState 대체 가능)

우선 Redux의 reducer 함수에서의 immer의 사용에 대해서 알아보자.

reducer함수는 이전 상태를 action을 통해 다음 상태로 만들어내는 역할을 한다.
이때 새로운 상태는 반드시 불변성을 지켜서 구현을 해줘야 하는데, 구조가 복잡한 객체의 불변성을 지켜서 상태값을 업데이트하는 것이 복잡한 경우에는 immer를 사용한다.

immer의 기본 사용 형태
기존 reducer함수의 switch-case 문을 produce의 내부에 작성해준다.

1
2
3
const reducer = (state = initialState, action) => {
return produce(state, (draft) => {});
};

immer를 사용하게 되면, 기존에 state로 작성했던 부분이 draft로 대체가 된다. draft는 별도의 불변성과 상관없이 작성을 해줘도 immer가 알아서 새로운 state를 불변성 지켜서 만들어준다. (state 인자는 건들이지 말고 draft만 조작하도록 한다 - return produce()부분이 불변성을 지킨 새로운 state 값을 반환해준다)

Read More

210525 React eslint 설정, shortid

 React eslint 설정

React ESLint

ESLint는 자바스크립트 문법 중에 에러가 있는 곳에 표시를 해주는 도구이다. 개발자가 직접 정의한 내용대로 코드를 점검하고 에러가 있으면 코드에 직접 표시를 해주기 때문에 매우 편리하며, 문법 에러뿐만 아니라 다른 개발자들과 협업을 할때 코딩 스타일을 정할 수 있어 매우 유용하다.

Read More