Backend ๐Ÿ“š/Node.js

[Node] ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ, ํ† ํฐ ์ƒ์„ฑ - Bcrypt, Jsonwebtoken

leejaejae 2024. 8. 1. 18:25

1.  ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค์—์„œ ์š”์ฒญํ•œ E-mail ์ฐพ๊ธฐ

- User.findOne()์œผ๋กœ ์ฐพ๊ธฐ

// index.js

...
app.post("/login", async (req, res) => {
    // ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค์—์„œ ์š”์ฒญ๋œ E-mail ์ฐพ๊ธฐ
  try {
    const user = await User.findOne({ email: req.body.email });
    if (!user) {  // E-mail์ด ์—†๋‹ค๋ฉด
      return res.json({
        loginSuccess: false,
        message: "์ œ๊ณต๋œ ์ด๋ฉ”์ผ์— ํ•ด๋‹นํ•˜๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.",
      });
    }
  })
})
...

 

 

2. ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค์—์„œ ์š”์ฒญํ•œ E-mail์ด ์žˆ๋‹ค๋ฉด ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๊ฐ™์€์ง€ ํ™•์ธ

1) ๋น„๋ฐ€๋ฒˆํ˜ธ ๋น„๊ต

// index.js

...
app.post('/login', async (req, res) => {
  try {
    const user = await User.findOne({ email: req.body.email });
  ...
    // ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค์—์„œ ์š”์ฒญํ•œ E-mail์ด ์žˆ๋‹ค๋ฉด ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๊ฐ™์€์ง€ ํ™•์ธ
    const isMatch = await user.comparePassword(req.body.password);
    console.log(isMatch);
    if (!isMatch) {  // ๋น„๋ฒˆ์ด ํ‹€๋ ธ์Œ
      return res.json({
        loginSuccess: false,
        message: "๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ํ‹€๋ ธ์Šต๋‹ˆ๋‹ค.",
      });
    }
  })
})
...


2) userModel์—์„œ comparePassword ๋ฉ”์†Œ๋“œ ๋งŒ๋“ค๊ธฐ

- Bcrypt ์ด์šฉํ•ด์„œ plain password๋ž‘ ์•”ํ˜ธํ™”๋œ(Hashed) ํŒจ์Šค์›Œ๋“œ๊ฐ€ ๊ฐ™์€์ง€ ํ™•์ธ

// User.js

...
// plainPassword, hashedPassword ๋น„๊ต
userSchema.methods.comparePassword = function (plainPassword) {
  // plainPassword: ์•”ํ˜ธํ™” ์ „ ๋น„๋ฒˆ(1234)  ์•”ํ˜ธํ™”๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ($2b$10$6UlU4XJY9/p8qF6xzhl)
  // plainPassword๋„ ์•”ํ˜ธํ™”ํ•ด์„œ ์•”ํ˜ธํ™” ๋œ ๋น„๋ฒˆ์ด๋ž‘ ์„œ๋กœ ๋น„๊ต(๋ณตํ˜ธํ™” ์‹œํ‚ฌ ์ˆœ ์—†์Œ)
  return new Promise((resolve, reject) => {
    bcrypt.compare(plainPassword, this.password, (err, isMatch) => {
      if (err) {   // ๋น„๋ฒˆ์ด ๋‹ค๋ฅด๋ฉด err
        reject(err);
      } else {  // ๋น„๋ฒˆ ๊ฐ™์œผ๋ฉด true๊ฐ’ ๋ฐ˜ํ™˜
        resolve(isMatch);
      }
    });
  });
};
...

 

 

3. ๋น„๋ฐ€๋ฒˆํ˜ธ๊นŒ์ง€ ๊ฐ™์œผ๋ฉด Token ์ƒ์„ฑ

1) Jsonwebtoken ์‚ฌ์šฉ

npm install jsonwebtoken --save


2) token ์ƒ์„ฑ

// index.js

...
app.post('/login', async (req, res) => {
  try {
    const user = await User.findOne({ email: req.body.email });
    ...
    // ๋น„๋ฐ€๋ฒˆํ˜ธ๊นŒ์ง€ ๊ฐ™์œผ๋ฉด Token ์ƒ์„ฑ
    const token = await user.generateToken();
  }
})
// User.js

...
// jsonwebtoken์„ ์ด์šฉํ•ด์„œ token ์ƒ์„ฑ
userSchema.methods.generateToken = function () {
  const user = this;
  const token = jwt.sign({ userId: user._id.toHexString() }, "secretToken"); // ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค ๋‚ด ์•„์ด๋”” ๋„ฃ์–ด์ฃผ๊ธฐ
  // user._id + 'secretToken' ํ•ด์„œ ํ† ํฐ์„ ๋งŒ๋“œ๋Š” ๊ฑฐ์ž„
  // ๊ทธ๋ž˜์„œ ๋‚˜์ค‘์— 'secretToken'๋กœ ์•„์ด๋”” ๊ฒ€์ƒ‰ํ•จ

  user.token = token;
  return user.save().then(() => token); // ํ† ํฐ์ด ์ž˜ ๋งŒ๋“ค์–ด์กŒ์„ ๊ฒฝ์šฐ user์ •๋ณด ๋„˜๊ฒจ์คŒ
};


3) ํ† ํฐ ์ฟ ํ‚ค์— ์ €์žฅ

npm install cookie-parser --save
// index.js

...
app.post("/login", async (req, res) => {
  try {
    const user = await User.findOne({ email: req.body.email });
    ...
    const token = await user.generateToken();
    res
      // ํ† ํฐ์„ ์ฟ ํ‚ค์— ์ €์žฅ(์ฟ ํ‚ค ๋ง๊ณ  ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€ ๋“ฑ ์‚ฌ์šฉ ๊ฐ€๋Šฅ)
      .cookie("user_auth", token)
      .status(200)
      .json({ loginSuccess: true, userId: user._id });
    })
  })
})

 

4. ์ตœ์ข…

1) ์ฝ”๋“œ

// index.js

const express = require("express");
const app = express();
const port = 4000;
const bodyParser = require("body-parser");
const cookieParser = require("cookie-parser"); 
const config = require("./config/key");

const { User } = require("./models/User");

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser()); 

const mongoose = require("mongoose");
mongoose
  .connect(config.mongoURI, {})
  .then(() => console.log("MongoDB connected.."))
  .catch((err) => console.log(err));

app.get("/", (req, res) => {
  res.send("์•ˆ๋…•ํ•˜์„ธ์š”. jaejae์ž…๋‹ˆ๋‹ค.");
});

app.post("/register", async (req, res) => {
  const user = new User(req.body);
  const result = await user
    .save()
    .then(() => {
      res.status(200).json({
        success: true,
      });
    })
    .catch((err) => {
      res.json({ success: false, err });
    });
});

app.post("/login", async (req, res) => {
  try {
    const user = await User.findOne({ email: req.body.email });
    if (!user) {
      return res.json({
        loginSuccess: false,
        message: "์ œ๊ณต๋œ ์ด๋ฉ”์ผ์— ํ•ด๋‹นํ•˜๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.",
      });
    }
    const isMatch = await user.comparePassword(req.body.password);
    console.log(isMatch);
    if (!isMatch) {
      return res.json({
        loginSuccess: false,
        message: "๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ํ‹€๋ ธ์Šต๋‹ˆ๋‹ค.",
      });
    }
    const token = await user.generateToken();
    res
      .cookie("user_auth", token)
      .status(200)
      .json({ loginSuccess: true, userId: user._id });
  } catch (err) {
    return res.status(400).send(err);
  }
});

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`);
});
// User.js

const mongoose = require("mongoose");
const bcrypt = require("bcrypt");
const saltRounds = 10;
const jwt = require("jsonwebtoken");

const userSchema = mongoose.Schema({
  name: {
    type: String,
    maxlength: 50,
  },
  email: {
    type: String,
    trim: true,
    unique: 1,
  },
  password: {
    type: String,
    minlength: 5,
  },
  lastname: {
    type: String,
    maxlength: 50,
  },
  role: {
    type: Number,
    default: 0,
  },
  image: String,
  token: {
    type: String,
  },
  tokenExp: {
    type: Number,
  },
});

// bcrypt๋กœ ๋น„๋ฐ€๋ฒˆํ˜ธ ์•”ํ˜ธํ™”
userSchema.pre("save", function (next) {
  let user = this;

  if (user.isModified("password")) {
    bcrypt.genSalt(saltRounds, function (err, salt) {
      if (err) return next(err);

      bcrypt.hash(user.password, salt, function (err, hash) {
        if (err) return next(err);
        user.password = hash;
        next();
      });
    });
  } else {
    next();
  }
});

// plainPassword, hashedPassword ๋น„๊ต
userSchema.methods.comparePassword = function (plainPassword) {
  return new Promise((resolve, reject) => {
    bcrypt.compare(plainPassword, this.password, (err, isMatch) => {
      if (err) {
        reject(err);
      } else {
        resolve(isMatch);
      }
    });
  });
};

// JWT ํ† ํฐ ์ƒ์„ฑ
userSchema.methods.generateToken = function () {
  const user = this;
  const token = jwt.sign({ userId: user._id.toHexString() }, "secretToken");

  user.token = token;
  return user.save().then(() => token);
};

const User = mongoose.model("User", userSchema);

module.exports = { User };


2) ๊ฒฐ๊ณผ ๐ŸŽ‰


โ˜… ๋”ฐ๋ผํ•˜๋ฉฐ ๋ฐฐ์šฐ๋Š” ๋…ธ๋“œ, ๋ฆฌ์•กํŠธ ์‹œ๋ฆฌ์ฆˆ - ๊ธฐ๋ณธ๊ฐ•์˜ ํด๋ก ์ฝ”๋”ฉ ์ž…๋‹ˆ๋‹ค.