๐ŸŽฏ ๋ผ์šฐํ„ฐ์™€ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ๋ถ„๋ฆฌํ•˜์—ฌ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ๋†’์ด๊ณ , crypto ๋‚ด์žฅ ๋ชจ๋“ˆ์„ ํ†ตํ•ด ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•”ํ˜ธํ™”ํ•ฉ๋‹ˆ๋‹ค.


๐Ÿ“™ Today I Learned

ํŒจํ‚ค์ง€ ์„ค์น˜ ๋ฐ ํด๋” ๊ตฌ์กฐ

์ถ”๊ฐ€ ํŒจํ‚ค์ง€

http-status-code ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•˜์—ฌ ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋ณ€์ˆ˜๋ช…์œผ๋กœ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

npm install http-status-code

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

controller๋ฅผ ํ†ตํ•ด์„œ ๋ผ์šฐํ„ฐ๋Š” ๊ฒฝ๋กœ ์„ค์ •๋งŒ ๋‹ด๋‹นํ•˜๊ณ , ์š”์ฒญ ์ฒ˜๋ฆฌ๋Š” ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ํ†ตํ•ด ์ˆ˜ํ–‰ํ•˜๋„๋ก ๋ถ„๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“‚ BOOK-SHOP
โ”œโ”€โ”€ ๐Ÿ“‚ controller
โ”‚   โ”œโ”€โ”€ ๐Ÿ“„ BookController.js
โ”‚   โ”œโ”€โ”€ ๐Ÿ“„ CartController.js
โ”‚   โ”œโ”€โ”€ ๐Ÿ“„ LikeController.js
โ”‚   โ”œโ”€โ”€ ๐Ÿ“„ OrderController.js
โ”‚   โ””โ”€โ”€ ๐Ÿ“„ UserController.js
โ”œโ”€โ”€ ๐Ÿ“‚ node_modules
โ”œโ”€โ”€ ๐Ÿ“‚ routes
โ”‚   โ”œโ”€โ”€ ๐Ÿ“„ books.js
โ”‚   โ”œโ”€โ”€ ๐Ÿ“„ carts.js
โ”‚   โ”œโ”€โ”€ ๐Ÿ“„ likes.js
โ”‚   โ”œโ”€โ”€ ๐Ÿ“„ orders.js
โ”‚   โ””โ”€โ”€ ๐Ÿ“„ users.js
โ”œโ”€โ”€ ๐Ÿ“„ .env
โ”œโ”€โ”€ ๐Ÿ“„ app.js
โ”œโ”€โ”€ ๐Ÿ“„ package-lock.json
โ””โ”€โ”€ ๐Ÿ“„ package.json

๐Ÿค” ์™œ ์ปจํŠธ๋กค๋Ÿฌ๋Š” PascalCase๋กœ ์ž‘์„ฑํ• ๊นŒ?

์ปจํŠธ๋กค๋Ÿฌ ํŒŒ์ผ์€ ํด๋ž˜์Šค์ฒ˜๋Ÿผ ํ•˜๋‚˜์˜ ๊ฐ์ฒด์ฒ˜๋Ÿผ ํŠน์ • ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ชจ๋“ˆ๋กœ์„œ์˜ ์—ญํ• ์„ ๊ตฌ๋ถ„ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ€๋…์„ฑ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•ด์„œ ์ฃผ๋กœ PascalCase๋กœ ์ž‘์„ฑํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.


users TABLE ์ˆ˜์ •

Table users {
  id integer [primary key]
  email varchar
  password varchar
  salt varchar
}

Salt(์†”ํŠธ)

๊ฐ™์€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ผ๋„ ๋งค๋ฒˆ ๋‹ค๋ฅธ ํ•ด์‹œ๊ฐ’์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ์ถ”๊ฐ€ํ•˜๋Š” ๋žœ๋ค ๋ฌธ์ž์—ด์ž…๋‹ˆ๋‹ค.




ํšŒ์› API ์ˆ˜์ •

๐Ÿ‘‰ ์ˆ˜์ •ํ•œ ๋ถ€๋ถ„์€ ์ฃผํ™ฉ์ƒ‰ ๊ธ€์”จ๋กœ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

ํšŒ์› ๊ฐ€์ž…

  • Method : POST

  • URL : /users/join

  • HTTP Status Code : 201 Created

  • Request Body

  {
    "email": "์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ์ด๋ฉ”์ผ",
    "password" : "์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ"
  }
  • Response Body : x

๋กœ๊ทธ์ธ

  • Method : POST

  • URL : /users/login

  • HTTP Status Code : 200 Ok

  • Request Body

  {
    "email": "์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ์ด๋ฉ”์ผ",
    "password" : "์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ"
  }
  • Response Cookie : JWT Token

๋น„๋ฐ€๋ฒˆํ˜ธ ์ดˆ๊ธฐํ™” ์š”์ฒญ

  • Method : POST

  • URL :/users/rest

  • HTTP Status Code : 200 Ok

  • Request Body

  {
    "email": "์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ์ด๋ฉ”์ผ"
  }
  • Response Body
  {
    "email": "์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ์ด๋ฉ”์ผ"
  }

๋น„๋ฐ€๋ฒˆํ˜ธ ์ดˆ๊ธฐํ™”

  • Method : PUT

  • URL : /users/rest

  • HTTP Status Code : 200 Ok

  • Request Body

  {
    "email": "์ด์ „ ํŽ˜์ด์ง€์—์„œ ์ž…๋ ฅํ•œ ์ด๋ฉ”์ผ",
    "password" : "์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ"
  }
  • Response Body : x



UserController.js

const conn = require('../mariadb');
const { StatusCodes } = require('http-status-codes');
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const dotenv = require('dotenv');
dotenv.config();

const join = (req, res) => {
  const { email, password } = req.body;

  // ์•”ํ˜ธํ™”๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ์™€ salt ๊ฐ’์„ ๊ฐ™์ด DB ์ €์žฅ
  const salt = crypto.randomBytes(10).toString('base64');
  const hashPassword = crypto
    .pbkdf2Sync(password, salt, 10000, 10, 'sha512')
    .toString('base64');

  const sql = 'INSERT INTO users (email, password, salt) VALUES(?, ?, ?)';
  const values = [email, hashPassword, salt];
  conn.query(sql, values, (err, result) => {
    if (err) {
      return res.status(StatusCodes.BAD_REQUEST).end();
    }

    res.status(StatusCodes.CREATED).json(result);
  });
};

const login = (req, res) => {
  const { email, password } = req.body;

  const sql = 'SELECT * FROM users WHERE email = ?';
  conn.query(sql, email, (err, result) => {
    if (err) {
      return res.status(StatusCodes.BAD_REQUEST).end();
    }

    const loginUser = result[0];

    const hashPassword = crypto
      .pbkdf2Sync(password, loginUser.salt, 10000, 10, 'sha512')
      .toString('base64');

    if (loginUser && loginUser.password === hashPassword) {
      const token = jwt.sign(
        {
          email: loginUser.email,
        },
        process.env.PRIVATE_KEY,
        {
          expiresIn: '5m',
          issuer: 'eunmi',
        }
      );

      res.cookie('token', token, {
        httpOnly: true,
      });

      console.log(token);

      return res.status(StatusCodes.OK).json(result);
    } else {
      return res.status(StatusCodes.UNAUTHORIZED).end();
    }
  });
};

๐Ÿ”’ ์•”ํ˜ธํ™” ๊ณผ์ •

ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ ๋กœ์ง์—์„œ๋Š” PBKDF2(Password-Based Key Derivation Function 2) ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•”ํ˜ธํ™”ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

1. salt ์ƒ์„ฑ

const salt = crypto.randomBytes(10).toString('base64');
  • crypto.randomBytes(10) : 10๋ฐ”์ดํŠธ ํฌ๊ธฐ์˜ ๋žœ๋ค ๊ฐ’์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

  • toString('base64') : Base64 ์ธ์ฝ”๋”ฉํ•˜์—ฌ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ( ์ด์ง„ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ๋žŒ์ด ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ณผ์ • )



2. PBKDF2๋ฅผ ์ด์šฉํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹ฑ

const hashPassword = crypto
  .pbkdf2Sync(password, salt, 10000, 10, 'sha512')
  .toString('base64');
  • pbkdf2Sync(์›๋ณธ ๋น„๋ฐ€๋ฒˆํ˜ธ, salt, ๋ฐ˜๋ณต ํšŸ์ˆ˜, ์ถœ๋ ฅ ๊ธธ์ด, ํ•ด์‹œ ์•Œ๊ณ ๋ฆฌ์ฆ˜)

    • password : ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ์›๋ณธ ๋น„๋ฐ€๋ฒˆํ˜ธ

    • salt : ์œ„์—์„œ ์ƒ์„ฑํ•œ ๋žœ๋ค ์†”ํŠธ

    • 10000 : ํ•ด์‹ฑ์„ ๋ฐ˜๋ณตํ•˜๋Š” ํšŸ์ˆ˜ (๋ฐ˜๋ณต ํšŸ์ˆ˜๊ฐ€ ๋†’์„์ˆ˜๋ก ํ•ดํ‚น ์‹œ๋„์— ๋Œ€ํ•œ ์ €ํ•ญ๋ ฅ์ด ์ฆ๊ฐ€)

    • 10 : ์ตœ์ข… ํ•ด์‹œ๊ฐ’์˜ ๊ธธ์ด (๋ฐ”์ดํŠธ ๋‹จ์œ„)

    • sha512 : SHA-512 ํ•ด์‹œ ์•Œ๊ณ ๋ฆฌ์ฆ˜


๐Ÿค” ์™œ DB์— hashPassword์™€ salt๋ฅผ ๊ฐ™์ด ์ €์žฅํ•ด์•ผํ• ๊นŒ?

๊ฐ™์ด ์ €์žฅํ•ด์•ผ ๋กœ๊ทธ์ธ ์‹œ์— ๊ฐ™์€ ์†”ํŠธ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์•”ํ˜ธํ™”๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ์™€ ๋น„๊ตํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.




โœ๏ธ ํ•œ ์ค„ ํšŒ๊ณ 

์•”ํ˜ธํ™”์— ๋Œ€ํ•œ ๊ฐœ๋…์„ ์ดํ•ดํ•˜๋ฉด์„œ ๋ณด์•ˆ์˜ ์ค‘์š”์„ฑ์„ ๋‹ค์‹œ๊ธˆ ๊นจ๋‹ฌ์•˜์Šต๋‹ˆ๋‹ค.

profile
๐ŸŒฑ๊ฐœ๋ฐœ ๊ธฐ๋ก์žฅ

0๊ฐœ์˜ ๋Œ“๊ธ€