Nodejs/Server

게시판 서버 만들기 #2 - 로그인

Sila 2022. 3. 19. 22:20

저번에 했던 회원 가입 기능에 이어 이젠 로그인 기능을 구현해보도록 하자.

 

1. 사용자가 입력한 아이디, 비밀번호를 프론트에서 백엔드 서버로 보낸다.

 

2. 백엔드 서버에서 받은 id, pw 를 DB로 보내 일치하는 데이터가 있는지 비교한다.

 

3.1 일치하는 데이터가 있다면 로그인에 성공한다

 

4. jwt를 생성, 쿠키화해서 브라우저에 준다.

 

3.2 일치하는 데이터가 없다면 에러 메시지를 출력한다.

 

1. intro

다음과 같이 login.html을 작성한다.

/*  front/views/login.html  */

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=], initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
    <form method="post" action="http://localhost:4001/api/user/login" id="login_frm">
        <ul>
            <li>
                <input type="text" name="userid" id="userid" value="sila">
            </li>
            <li>
                <input type="text" name="userpw" id="userpw" value="1234">
            </li>
        </ul>
        <input type="submit" value="login">
    </form>
</body>
</html>

 

2. 로그인 기능 구현

2.1 입력한 데이터를 프론트에서 백 엔드 서버로 제출

login.html에서 입력한 정보를 백 엔드 서버로 전송하는 script를 추가해준다.

 

/*  front/view/login.html  */

<script type='text/javascript'>
   const login_frm = document.querySelector('#login_frm')
   
   login_frm.addEventListener('submit', async (e) => {
      e.preventDefault()
      
      const userid = document.querySelector('#userid')
      const userpw = document.querySelector('#userpw')
      
      const data = {
         userid: userid.value,
         userpw: userpw.value
      }
      
      const option = {
         'Content-type' : 'application/json',
         withCredentials : true
      }
      
      try {
         const response = await axios.post('http://localhost:4001/api/user/login', data, option)
      }
      catch (e) {
         console.log(e.message)
      }
      
</script>

이제 백엔드로 가서 요청을 받고 응답을 줄 코드를 작성하자.

 

2.2 백 엔드 서버에서 받은 id, pw를 DB의 데이터와 비교

/*  back/server.js  */

app.post('api/user/login', async (req, res) => {
   const {userid, userpw } = req.body
   const sql = 'SELECT userid, name, nickname from user where userid=? and userpw=?'
   const param = [userid, userpw]
   
   try {
      const [result] = await pool.execute(sql, param)
   
      if (result.length == 0) { throw new Error('id/pw를 확인해주세요.') }

      const resonse = {
         result,
         errno:0
      }
      
      res.json(response)
   }
   catch (e) {
       const response = {
          errno : 1
       }
       res.json(response)
   }
})

 

sql 구문을 수행했을 때 id, pw가 일치하는 데이터셋이 있다면 이를 결괏값으로 가져올 것이다.

 

이 경우, result.length의 값은 1이 된다. (동일한 id는 많아야 1개 존재하므로)

 

이 때는 result와 함께 errno에 0 이라는 값을 줘서 함께 프론트 서버로 준다. 

 

result가 없을 경우 (일치하는 id, pw를 가진 데이터셋이 없을 경우) errno 값에 1을 주고 프론트 서버로 넘긴다.

 

(회원가입 때와 동일하게 되면 0, 안되면 1 주는 메커니즘과 동일)

 

다시 프론트 서버로 돌아가 각각의 경우에 대한 반응을 만들자.

 

try 안에 다음과 같이 추가한다.

 

/*  front/views/login.html  */

try {
   const response = await axios.post('http:localhost:4001/api/user/login', data, option)
   
   if (response.data.errno !== 0) { throw new Error('id/pw를 확인해주세요') }
   
   alert(${response.data.result[0].nickname}님 환영합니다.')
   location.href='http://localhost:3001')
}

여기까지 해봐서 잘 작동하면 이제 jwt와 쿠키 생성 기능을 추가해야 한다.

 

2.3 jwt, 쿠키 생성

https://liferesetbutton.tistory.com/30

 

우선 토큰을 만드는 것부터 다시 해보자.

 

back 폴더에 utils 폴더를 만들고, 그 안에 jwt.js를 만들자.

 

/*  back/utils/jwt.js  */

require('dotenv').config()

const crypto = require('crypto')
const salt = process.env.SALT || 'sila'

function createToken(data) {
    const header = {
        tpy:'JWT',
        alg:'HS256'
    }

    const payload = {
        ...data
    }

    const eHeader = encoding(header)
    const ePayload = encoding(payload)
    const signature = createSignature(eHeader, ePayload)

    return `${eHeader}.${ePayload}.${signature}`
}

function encoding(data) {
    return Buffer.from(JSON.stringify(data))
    .toString('base64').replace(/[=]/g, '')
}

function createSignature(header, payload) {
    const encoding = `${header}.${payload}`
    const signature = crypto.createHmac('sha256', salt).update(encoding).digest('base64')
                      .replace(/[=]/g,'')

    return signature
}

module.exports = {
    createToken,
    createSignature
}

salt 또한 보안에 관계되는 변수이므로 .env에 넣어주는 것이 좋다.

 

이를 export하고, back/server.js에서 이를 import할 것이다.

 

다음과 같이 한 줄 추가해주자.

 

/*  back/server.js  */

const { createToken } = require('./utils/jwt.js')

 

그리고 가져온 createToken 함수를 라우터 안에서 사용한 후, 생성된 token을 쿠키로 만들어서 브라우저에 준다.

 

/*  back/server.js  */

app.post('api/user/login', async (req, res) => {
   // 중략
   try {
      // 중략
      if ( result.length == 0) { throw new Error('id/pw 불일치') }
      
      const jwt = createToken(result[0])
      
      res.cookie('token', jwt, {
         path:'/',
         httpOnly: true,
         domain: 'localhost'
      })
      
      // ...후략

})

 

여기까지 하고 로그인을 시도해보자. 성공했을 때 쿠키가 생성된다면 문제가 없는 것이다.