즐코

JWT로 로그인 기능 구현해보기 본문

NodeJS

JWT로 로그인 기능 구현해보기

YJLEE_KR 2022. 3. 6. 17:58

저번 미니 프로젝트 복습 겸 사용자계정을 DB에서 당겨오는 방식으로 JWT 로그인 기능 구현을 연습해보았다.

 

사실 router 분리를 해야하지만 로그인 기능만 구현할 거니까 server.js 파일에 다 때려박았다.

이번엔 async await를 써보자고 마음먹었다.. 우선 써보기전에 원래 했던대로 콜백지옥을 만들어보앗다.

 

pool.getConnection의 인자로 콜백함수가 들어가고,

conn.query에도 3번째 인자로 콜백함수가 들어가니 그안에서 if문까지 써버리면 엄청난 depth가 생긴다.

특히, pool과의 connection을 다시 return해주는 conn.release()를 어디 괄호뒤에 써야할지 찾는 것도 힘들다.

 

 

 

그래서 async, await으로 바꿔보았다.

어떻게 바꿀지 감이 안잡혀서 다른 팀의 깃헙을 참고해서 공부했다.

https://github.com/green-kong/team6_login_board 

 

 

1. pool 생성하기 

 

우선, mysql이 아니라 promise를 지원해주는 mysql2 라는 모듈이 필요해서

npm install mysql2 또는 yarn add mysql2를 해준다. 

 

그리고 불러올때는, const mysql = require('mysql2')가 아니라,

const mysql = require('mysql2/promise') 이런식으로 가져온다. db.js에서 따로 pool을 생성해주고 내보낸다.

 

 

 

 

2. pool 가져와서 async, await으로 server.js 파일 작성

 

async를 logincheck라는 함수앞에 붙여서 promise 를 반환하게끔 만들어준다.

await은 async 함수 안에서만 쓸 수 있는데, promise.then과 비슷한 역할이다. 말 그대로 기다린다 라는 뜻,

 

promise 객체인 pool.getConnection((err,conn)=>{})의 결과값을 변수 conn에 담아줬다.

const conn = await pool.getConnection()

 

쿼리문으로 데이터를 가져오는 conn.query(SQL.checkLogin,param,(err,result)=>{})의 결과값도 변수 result에 담아줌

const [ result ] = await conn.query(SQL.checkLogin, param)

 

이 때, result는 배열 안에 배열이 담겨져있는 형태 [[result]]로 나오므로 배열의 구조분해할당을 이용해서 배열을 한꺼풀 벗겨주었다. 

 

if 문에서 아이디나 비번이 틀린 것을 잡아서 에러 처리를 하였고, 

if (result.length === 0) throw new Error('아이디가 존재하지 않습니다')

 

그 이후구문은 else구문(아이디/비번 일치할때) 인데 else를 생략한 것이다.

JWT를 만들기 위해 DB에서 가져온 사용자의 데이터 result[0] 중에 공개해도 괜찮은 데이터를 payload 객체에 담아준다.

 

Token을 만드는 코드가 꽤 길어서 따로 빼주었다.

const token = createToken(payload)

 

그다음 토큰을 쿠키에 담아줄 차례이다.

res.setHeader('Set-cookie', `AccessToken=${token}; HttpOnly; Secure; Path=/;') 

 

conn.release 는 로그인 실패/성공 여부에 상관없이 무조건 마지막에 처리해줘야 하므로, finally로 처리해줌

 

 

 

3. 토큰 만드는 함수 createToken 따로 뺌 (util/jwt.js)

 

해쉬화 과정에서 쓰이는 salt도 env 파일로 빼주었다. 

근데 이때, jwt.js파일 상에서 require('dotenv').config() 했더니 .env파일에서 salt를 가져오지 못했다.

찾아보니 .env가 현재 경로가 아닌 다른 곳에 있을 경우엔 path, 경로를 명시해줘야했다. 

config의 인자로 {path:"env파일 경로"} 를 넣어준다.

 require('dotenv').config({path:"../.env"})

 

 

인코딩하는 과정도 반복해서 쓰이므로 따로 함수로 빼주었다.

 

 

signature만드는 과정도 함수로 따로 빼줌

 

 

토큰 만드는 코드, 

인자로 들어가는 state에는 위에서 로그인에 성공한 사용자의 공개데이터를 payload에 넣는거다.

 

 

위의 토큰 생성 코드들은 이미 전 포스팅에서 정리했으므로 설명은 생략

 

이렇게 하면 로그인 시 AccessToken이 부여된다. 

 

 

 

4. 토큰 검증하기 위한 미들웨어 auth 만들어주기 

 

마지막으로,

그 이후에 누군가 payload쪽 내용을 조작하는 걸 방지하기 위해 auth라는 토큰 검증 미들웨어를 만들어주자.

 

1- 사용자의 쿠키에 있는 토큰의 헤더와 페이로드값을 이용해서 검증용 시그니처를 만들어주고

2- 사용자의 쿠키에 있는 토큰의 시그니처값과 검증용 시그니처를 비교하면 끝

 

 

예를 들어 관리자 페이지에서 auth 미들웨어를 넣는다면, 

admin router 상에선 토큰 검증을 따로 하지않고 level 검사만 해줄수 있겠다.

 

Comments