해당하는 개념들은 검색을 하면 많은 정보가 나오기 때문에 제 프로젝트에 어떻게 적용시킬 것이며, 간단하게 이해 위주의 설명만 첨언 하겠습니다.
토큰에 대한 인증 방식은 해당 그림으로 우선 대체 후에 진행하겠습니다.
지금까지의 구현 해본 session과 cookie를 이용한 인증 방식의 흐름은
클라이언트가 아이디와 비밀번호를 입력하여 서버에 요청을 한다.
서버에서는 요청받은 아이디와 비밀번호를 DB에 조회 후에 존재하면 서버 DB에 session을 생성 한 후에 해당 하는 session의 key 값을 클라이언트에게 쿠키로 전달한다.
이후에 session 유지를 위해 쿠키의 value 값은 session의 key값이므로 쿠키를 이용하여 session의 유지상태를 조회한다.
이 방식의 문제점은 cookie의 보안성을 session이라는 저장소를 통해 어느정도 해결은 하였으나,
벌써 위의 과정에서만 DB 조회를 2번이나 하므로, 많은 유저가 로그인을 하고, 또 로그인의 session을 체킹하기 위해서는 계속하여 DB를 조회하는 상황이 발생합니다.
(DB 조회는 적으면 적을수록 좋다고 합니다. 대부분 서버가 down 되는 상황이 DB때문이라고 합니다.)
이러한 DB조회를 줄이기 위해 나온 방식이 토큰 인증 방식이라고 생각합니다.
client 측에서 로그인 요청을 하면, 서버는 로그인 정보에 따라 클라이언트에게 토큰을 발급해줍니다. (보통 해당 서버만 decoding을 할 수 있게 암호화를 진행합니다.)
앞으로 클라이언트는 서버에 요청을 보낼 때 헤더에 token을 담아서 보냅니다.
서버는 헤더의 token 부분만 해석하여, 본인이 발급한 token이 맞으면 로그인된 클라이언트라고 판단합니다.
이러한 토큰이 JSON으로 이루어진 토큰을 JWT라 칭합니다.
Base64 URL-safe Encode를 통해 인코딩하여 직렬화 한 것이라고 하는데, JWT의 가장 큰 특징은 3가지 영역입니다.
대표적으로 로그인 시스템을 만들 때 가장 거론이 많이되는 두 라이브러리이다.
벼르고 벼르다 드디어 학습을 하게 되었는데, JWT의 가장 강력한 기능 중 하나라고 생각합니다.
이미지 출처
어느 순간 갑자기 다른 계정아이디로 로그인이 가능한 플랫폼이 늘어났습니다.
평소에는 대수롭지 않게 되겠거니~ 하고 넘겼지만, 세션과 쿠키를 활용한 로그인 시스템을 직접 구축하다보니 이게 어떻게 되는거지?란 생각이 들었습니다.
토큰의 가장 강력한 기능 중에 하나인 "DB조회가 적고, 서버단에서 인증이 이루어진 것처럼 행동이 가능해진다."를 이용한 인증 방식입니다.
다음과 같은 그림으로 이루어지는데 쉽게 생각하면 A, B, C 서비스가 있는데 A서비스의 계정으로 B에게 로그인 요청을 하면 A와 B가 토큰을 이용해 로그인여부를 확인하는 기술입니다.
사용자는 github OAuth 서버로 로그인을 요청 후에 Authorization code를 발급 받은 후 서버에 전달합니다.
서버는 Authorization code로 github 서버에 이 유저가 github 유저가 맞는가? 확인을 부탁합니다. (token을 요청한다고 생각하시면 좋습니다. 2가지 token이 오는데 이는 추후에 OAuth를 정리할 때 알아보도록 합시다.)
받은 해당 git 서버의 access token으로 git 서버에 해당 유저의 이름, 이메일 등의 정보를 요청합니다.
요청받은 유저의 데이터로 DB에 조회한 후 존재하느 유저면 JWT를 생성합니다.
이제 JWT를 클라이언트에게 전달합니다.
이후에 통신에는 JWT가 헤더에 존재하므로 위의 JWT와 동일합니다.
"저는 이를 클라이언트가 로그인을 요청하면 해당 유저의 정보로 A서버에서 B서버로 token을 통한 로그인 요청을 대신해준다고 이해했습니다."
오류가 많을 수도 있지만, OAuth는 대리 로그인 시스템이라고 이해했습니다.
(클라이언트가 2번하는 일을 서버끼리 통신으로 생략한 것이죠.)
이는 물론 토큰을 통한 인증방식이기에 가능하다고 생각합니다.
서로 다른 두 서비스끼리 아이디와 비밀번호로 대리 로그인을 진행해버리면 개인정보와 보안에 심각한 결함이죠..
"옵저버란 스타크래프트 프로토스의 유닛으로 적들을 관찰하기 위해 탄생한 유닛이다. 테란전에서 필수 유닛이며"
출처: https://pjh3749.tistory.com/266 [JayTech의 기술 블로그:티스토리]
직역하면 "관찰자 패턴"이다.
FE개발에서 가장 자주 사용되는 패턴인데, 개인적인 관심사 중 하나인 FE에서 각 component의 상태를 확인하고, 상태에 따라 업데이트 하는 것이 가장 어려웠었다.
이제 이를 한 Observer를 두고 이 Observer에 구독과 발행을 통해 state를 발행 해주고, 관찰 중에 state에 변화가 일어나면 이에 맞추어 업데이트 하는 것을 중점을 둔 패턴이라고 할 수 있다.
class Observable {
constructor() {
this.observers = []
}
subscribe(func) {
this.observers.push(func)
}
unsubscribe(func) {
this.observers = this.observers.filter(observer => observer !== func)
}
notify(data) {
this.observers.forEach(observer => observer(data))
}
이러한 방식으로 각 state를 관리하게 되는데, 이제 state에 변화가 일어나는 Event가 발생하면 어떻게 업데이트가 이루어질까?
Observable에 Observer를 추가한다.
이벤트를 등록한 후 옵저버에 이벤트를 발행한다.
옵저버는 해당하는 이벤트를 받아 처리합니다.
그런데 이러한 변화는 언제 가장 자주 일어날까?
=> 보통은 데이터의 변화에 따라 새롭게 Component들이 업데이트가 일어난다.
"이벤트 발생" -> 상태 변경 (Model) -> 화면 변화 (View)
이런 상태에서는 Model이 Observable이 되고, View는 Observer가 된다.
실제로 프로젝트를 진행할 때에 view를 여러개로 나누기도 하지만 model 또한 어플리케이션이 커질수록 나누어야 하는 경우가 생긴다.
이 경우에는 Model의 상태가 변화하면 클라이언트에게 view에게 Notify하여 새롭게 업데이트하는 방식을 사용하기도 한다.
나에게 아무자료도 안주고 해당하는 Observer패턴을 만들라고 한다면, 제일 처음 class를 떠올렸을 것 같다.
당연히 react js에서도 클래스형 컴포넌트로 관리를 하겠거니 하고 찾아보았는데 놀랍게도, 최근에 함수형 컴포넌트로 이러한 state를 관리 할 수 있게 추가되었다고 한다.
이를 "react hook" 이라고 한다.
Class Component
import React, { Component } from 'react';
class App extends Component {
state = {
count: 0,
};
countUpdate(n) {
this.setState({
count: n,
});
}
render() {
const { count } = this.state;
return (
<div>
<div>
<h1>{count}</h1>
<button
onClick={() => {
this.countUpdate(count + 1);
}}
>
증가
</button>
</div>
</div>
);
}
}
export default App;
Hooks(react)
import React, { Component, useState } from 'react';
const App = () => {
const [count, setCount] = useState(0);
return (
<>
{count}
<button onClick={() => setCount(count + 1)}>증가</button>
</>
);
};
export default App;
"그래 함수형을 최근에 hook이라는 state관리가 가능한 건 알겠는데 왜 굳이 클래스형에서 함수형으로 변경한것인가?
우선 가장 치명적인 오류가 하나 있습니다.
Obsever pattern이기에 생긴 오류이기도 한대
class ProfilePage extends React.Component {
showMessage = () => {
alert(`${this.props.user} 를 팔로우 했습니다`);
}
handleClick = () => {
setTimeout(this.showMessage, 5000);
}
render() {
return <button onClick={this.handleClick}>Follow</button>;
}
}
해당 하는 방식으로 A유저를 구독하고 구독 로직이 실행되기 전에 B유저의 창으로 이동해버리면 B유저가 구독이 되어버린다고 합니다.
why? this는 이동해버린 순간 B 이기 때문
function ProfilePage(props) {
showMessage = () => {
alert(`${props.user}를 팔로우 했습니다`);
}
handleClick = () => {
setTimeout(showMessage, 5000);
}
return (
<button onClick={handleClick}>Follow</button>
)
}
다음과 같은 함수형은 어차피 상태 자체를 return하는 방식이기 때문에 정상적으로 알림이 갑니다.
출처 : 함수형 컴포넌트와 클래스, 어떤 차이가 존재할까?
점점 웹서비스가 커지고 Component가 증가하기 때문에, class를 활용한 state 관리가 너무 복잡해졌다고 합니다.
또한 함수형프로그래밍의 특징인 코드의 간결성, 재사용성이 우수합니다.
최근에 "Node js 와 mysql은 궁합이 안좋다"는 소리를 들었습니다.
왜 그런 걸까요?
이는 동기와 비동기 그리고 블록킹과 논블록킹에 대한 학습이 되어있다고 가정하고 글을 적겠습니다.
Node js는 Async하게 동작하는건 너무나도 유명하고 잘 알려진 사실입니다.
즉 A, B 함수가 실행하면 A를 실행하면서 B도 실행이 비동기적으로 이루어집니다. (사실 싱글스레드 이긴 합니다.)
우리가 원하는 Node js 의 모습은 DB의 데이터를 조회하고, 조회하는 동안 함수를 실행하는 이상적인 모습이지만, 실제로 Mysql 드라이버는 논블록킹으로 이루어져 있다고 합니다.
조회하는데 10초가 걸리는 쿼리문이 있다고 가정해봅시다.
그리고 유저 10명이 동시에 해당 데이터를 요청합니다.
비동기적인 실행이라면, 평소의 node js 라면 10명의 데이터를 비동기적으로 실행 후 약 10초가 걸리겠죠 ?
그러나 mysql 드라이버는 논블록킹입니다.
한명의 유저를 조회하기 위해서 쿼리문을 날리는 순간 해당하는 유저의 데이터를 받아오기 전까지는 제어권을 넘기지 않기 때문에 위와 같은 이상적인 상황이 나오지 않고 조회 => 결과, 조회 => 결과 가 이루어집니다.
즉 100초가 걸리는 아주 안좋은 상황이 발생하게 됩니다.
그렇기 때문에 async + non-blocking 조합을 잘 사용하지 않습니다.
이러한 점을 해결하기 위해 mysql2 에서는 connection pool이라는 기능을 제공합니다. (이를 해결하기 위한 기능인지는 모르겠습니다.)
connection pool을 이용하여 10명의 유저의 쿼리문을 10개를 날립니다.
이를 node js 의 Promise.all 을 활용하여 유저 10명의 데이터를 한번에 받아오는 것이 가능해집니다.
const mysql = require('mysql2');
(async function main(param) {
try {
const nativePool = mysql.createPool({
host: 'localhost',
user: 'root',
database: 'newsdb',
connectionLimit: 10,
});
const pool = nativePool.promise();
const query1 = pool.query('SELECT * FROM users');
const query2 = pool.query('SELECT * FROM users');
const query3 = pool.query('SELECT * FROM users');
const results = await Promise.all([query1, query2, query3]);
results.forEach(([rows, fields]) => console.log(rows));
} catch (error) {
console.error(error);
}
})();
https://stackoverflow.com/questions/62476886/mysql-not-running-with-promise-all-in-node-js
이후에 connection pool에 대해 학습하시면 더욱 좋습니다.
Connection Pool 이란?