Node.js - 로그인, 회원 가입

mingyu Lim·2024년 1월 8일
0

node.js

목록 보기
5/5

로그인 처리

  • POST 메소드를 이용하여 DB에 있는 ID와 PW가 맞는 지 확인하고, bcrypt를 이용한 암호화와 JWT 토큰을 이용한 로그인 처리 방법

bcrypt

npm i bcrypt

단방향의 특징을 가진 해시 함수를 이용하여, 입력받은 값을 해시를 통해 암호화를 하여 저장된 값과 비교해서 검증를 도와주는 라이브러리이다.

  1. 사용법
const bcrypt = require("bcrypt") or import bcrypt from "bcrypt"

// 비밀번호 암호화
// bcrypt( 해시하려는 값, 해시를 반복하는 횟수, 입력값을 해시한 후 실행할 함수);


const hashedPW = bcrypt.hash(pw, 10, (err, hash) => {
	try{
	  //해시화된 비밀번호(hash)를 DB에 저장하거나 다른 처리
	}
	catch(err){
	// 오류처리
	}
  
 // 비밀번호 확인

  const isMatch = await bcrypt.compare(password, user.password);
  	if(!isMatch){
      // 오류처리
    }
	

JWT 토큰

JWT은 JSON 객체에 인증에 필요한 정보들을 담고 비밀키로 만든 토큰으로, 인터넷 표준 인증 방식이며, 인증과 권한허가 방식들로 사용이 된다.

jsonwebtoken

JWT토큰을 구현하기 위해 jsonwebtoken라이브러리를 사용했다.

  • 설치
npm i jsonwebtoken
  • 암호화
// jwt.sign( 페이로드, 비밀키, [옵션, 콜백])
// 1시간 동안 유효하고, 사용자 이름을 담고 있는 토큰
const token = jwt.sign({name: "Kim"}, "secret Key", { expiresln: "1h"});
  • 복호화

//jwt.verify(토큰, 비밀키, [옵션])
jwt.verify(token, "secret_key", function (err, decoded) {
  console.log(decoded);
 });

  //   {
  //     userId: '12345',
  //     email: 'kagrin97@gmail.com',
  //     iat: 1674784695, 시작 시간 (issued at)
  //     exp: 1674788295  만료 시간 (Expiration)
  //   }

cookie-parse 모듈은 요청된 쿠키를 쉽게 사용할 수 있도록 도와주는 모듈 이다. express(req, res) 객체에 cookie를 사용할 수 있도록 기능을 부여한다.

  • 사용법
  1. 읽기
app.use(cookieParser());
console.log(req.cookies);

cookieParser()를 통해 해석된 쿠기는req.cookies를 통해 읽을 수 있다.

  1. 쿠키 만들기 (쓰기)
// res.cookie(키, 값, 옵션)
res.cookie("name","nile", {
  httpOnly: true,
})

쿠기 옵션

  • maxAge: 만료 시간 (밀리초 단위)
  • expires: 만료 날짜 (GMT 시간 설정)
  • path: 쿠키의 경로 초기 값은 "/"
  • domain: 도메인 네임 초기 값은 "loaded"
  • secure: https에서만 쿠키를 사용할 수 있도록 한다.
  • httpOnly: 웹 서버를 통해서만 쿠키에 접근 가능할 수 있다.
  • signed: 쿠키에 대한 서명을 지정
  1. 쿠키 삭제
    쿠키를 지우기 위해서 키, 값, 옵션이 정확히 일치해야만 삭제가 가능하다.(expire, maxAge는 제외)
res.clearCookie(,, 옵션)

실습 (로그인, 회원 가입)

초기 세팅 (Model, View)

  • app.js
    loginRouter 설정
const express = require("express");
const dbConnect = require("./config/dbConnect");
const bodyParser = require("body-parser");
const methodoverride = require("method-override");
const app = express();

// ejs view engine 세팅
app.set("view engine", "ejs");
app.set("views", "./views");

app.use(express.static("./public"));

app.use(methodoverride("_method"));

dbConnect();

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use("/", require("./routes/loginRouter"));

app.listen(3000, () => {
  console.log("서버 실행 중");
});
  • 스키마 세팅 (userModel.js)
    DB에 들어갈 유저 정보의 틀 설정
const mongoose = require("mongoose");
const Schema = mongoose.Schema;

const UserSchema = new Schema({
  username: {
    type: String,
    required: true,
    unique: true,
  },
  password: {
    type: String,
    required: true,
  },
});

module.exports = mongoose.model("User", UserSchema);
  • 로그인 화면 (home.ejs)
<%- include("./include/_header")%>

<main id="site-main">
  <div class="home-container">
    <h3>로그인</h3>
    <p>로그인이 필요한 서비스입니다.</p>

    <form action="/" class="login" method="POST">
      <label for="username"><b>Username</b></label>
      <input
        type="text"
        placeholder="사용자 아이디"
        name="username"
        id="username"
      />
      <label for="password"><b>Password</b></label>
      <input
        type="password"
        placeholder="비밀번호"
        name="password"
        id="password"
      />

      <button type="submit">로그인</button>
    </form>
  </div>
</main>

<%- include("./include/_footer")%>
  • 회원 가입 화면 (register.ejs)
<%- include("./include/_header")%>

<!-- Main -->
<main id="site-main">
  <h3>사용자 등록</h3>

  <form action="/register" class="register" method="POST">
    <label for="username"><b>아이디</b></label>
    <input type="text" placeholder="아이디" name="username" id="username" />
    <label for="password"><b>비밀번호</b></label>
    <input
      type="password"
      placeholder="비밀번호"
      name="password"
      id="password"
    />
    <label for="password2"><b>비밀번호 확인</b></label>
    <input
      type="password"
      placeholder="비밀번호 확인"
      name="password2"
      id="password2"
    />
    <input type="submit" value="등록" class="register-btn" />
  </form>
</main>
<!-- /Main -->
<%- include("./include/_footer")%>

Controller, Router

  • loginRouter.js
const express = require("express");
const router = express.Router();
const {
  getLogin,
  loginUser,
  getRegister,
  registerUser,
} = require("../controller/loginController");

router.route("/").get(getLogin).post(loginUser); 
router.route("/register").get(getRegister).post(registerUser);

module.exports = router;
  • loginController.js

const asyncHandler = require("express-async-handler");
const User = require("../models/userModel"); // 유저 정보 스키마
const bcrypt = require("bcrypt"); // 비밀번호 암호화 해시
require("dotenv").config(); // env 환경 변수 읽어오기
const jwtSecret = `${process.env.JWT_SECRET}`; // jwt Secret code
const jwt = require("jsonwebtoken"); // jwt 모듈 설정

// GET login page

const getLogin = (req, res) => { 
  res.render("home");
};

// POST Login user
const loginUser = asyncHandler(async (req, res) => {
  const { username, password } = req.body; 
  
  // 요청의 username과 일치하는 username을 DB에서 찾아 가져온다.
  const user = await User.findOne({ username }); 

  // 일치하는 유저가 없을 경우 오류 처리
  if (!user) {
    return res.json({ message: "일치하는 사용자가 없습니다." });
  }
	
  // 회원 가입시 설정한 비밀번호의 해시값과 입력한 해시 값 대조
  const isMatch = await bcrypt.compare(password, user.password);
  if (!isMatch) {
    return res.json({ message: "비밀번호가 맞지 않습니다." });
  }
  
  // 맞을 경우 유저의 토큰을 쿠키에 담아 유저에 보낸 후, contacts화면으로 리다이렉트
  const token = jwt.sign({ id: user._id }, jwtSecret);
  res.cookie("token", token, { httpOnly: true });
  res.redirect("/contacts");
});

// 회원 가입

// Resiter Page Get
const getRegister = (req, res) => {
  res.render("register");
};

// Resiter user POST /register
const registerUser = asyncHandler(async (req, res) => {
  const { username, password, password2 } = req.body;
  
  // 입력한 비밀번호 확인이 맞을 경우
  if (password === password2) {
    // 입력 받은 비밀 번호를 10번 해싱
    const hashedPassword = await bcrypt.hash(password, 10);
    // username과 해시한 비밀번호를 DB에 저장
    const user = await User.create({ username, password: hashedPassword });
    // 회원 가입 완료
    res.json({ message: "Resiter successful", user });
  } 
  // 입력한 비밀번호 확인이 틀릴 경우 오류 처리
  else { 
    res.send("fail");
  }
});

module.exports = { getLogin, loginUser, getRegister, registerUser };
  • 실제로 일단 만들어 보자는 마음으로 이 수업을 듣고 있지만, JWT, bcrypt 등 구글링을 해보면 아직 많은 내용들이 있어, 차차 하나씩 더 찾아보아야겠다.
  • 아직까지 이유를 찾지 못한 jwt Secret code를 템플릿 리터럴을 이용하지 않으면 아래와 같은 오류가 생기는 점을 발견하였다.
    오류 : secretOrPrivateKey

0개의 댓글