dh_0e

[Node.js] 강의 내용 개념 정리(5) (인증, 인가, 사용자 인증 미들웨어) 본문

내일배움캠프/Node.js[숙련]

[Node.js] 강의 내용 개념 정리(5) (인증, 인가, 사용자 인증 미들웨어)

dh_0e 2024. 5. 27. 19:45

인증(Authentication)

  • 인증은 서비스를 이용하려는 사용자가 인증된 신분을 가진 사람이 맞는지 검증하는 작업을 뜻하며 일반적으로 신분증 검사 작업에 해당함

 

인가(Authorization)

  • 인가는 이미 인증된 사용자가 특정 리소스에 접근하거나 특정 작업을 수행할 수 있는 권한이 있는지를 검증하는 작업을 뜻하며 놀이공원에서 자유 이용권을 소지하고 있는지 확인하는 단계로 볼 수 있음
  • 사용자 인증 미들웨어를 통해서 구현함

 

bcrypt 모듈

  • 입력받은 데이터를 특정 암호화 알고리즘을 이용하여 암호화 및 검증을 도와주는 모듈
  • 비밀번호를 bctypt를 이용해 암호화하게 된다면 특정한 문자열로 변환됨
  • 이 변환된 문자열은 단방향 암호화되어 원래의 비밀번호로 복수할 수 없게됨

 

bcrypt 암호화

import bcrypt from 'bcrypt';

const password = 'Sparta'; // 사용자의 비밀번호
const saltRounds = 10; // salt를 얼마나 복잡하게 만들지 결정합니다.

// 'hashedPassword'는 암호화된 비밀번호 입니다.
const hashedPassword = await bcrypt.hash(password, saltRounds);

console.log(hashedPassword);

 

bcrypt 복호화

import bcrypt from 'bcrypt';

const password = 'Sparta'; // 사용자가 입력한 비밀번호
const hashed = '$2b$10$OOziCKNP/dH1jd.Wvc3JluZVm7H8WXR8oUmxUQ/cfdizQOLjCXoXa'; // DB에서 가져온 암호화된 비밀번호

// 'result'는 비밀번호가 일치하면 'true' 아니면 'false'
const result = await bcrypt.compare(password, hashed);

console.log(result); // true

// 비밀번호가 일치하지 않다면, 'false'
const failedResult = await bcrypt.compare('FailedPassword', hashed);

console.log(failedResult); // false

 

이를 이용한 회원가입 / 로그인 구현

/** 사용자 회원가입 API **/
router.post("/sign-up", async (req, res, next) => {
  const { email, password, name, age, gender, profileImage } = req.body;
  const isExistUser = await prisma.users.findFirst({
    where: { email },
  });
  if (isExistUser) {
    console.log(isExistUser.email);
    return res
      .status(409)
      .json({ errorMessage: "이미 존재하는 이메일입니다." });
  }

  const hashedPassword = await bcrypt.hash(password, 10);

  const user = await prisma.users.create({
    data: {
      email,
      password: hashedPassword,
    },
  });

  const userInfo = await prisma.userInfos.create({
    data: {
      UserId: user.userId,
      name,
      age,
      gender: gender.toUpperCase(),
      profileImage,
    },
  });

  return res.status(201).json({ message: "회원가입이 완료되었습니다." });
});

/* 로그인 API */
router.post("/sign-in", async (req, res, next) => {
  const { email, password } = req.body;

  const user = await prisma.users.findFirst({ where: { email } });
  if (!user) {
    return res
      .status(401)
      .json({ errorMessage: "존재하지 않는 이메일입니다." });
  }

  if (!(await bcrypt.compare(password, user.password))) {
    return res
      .status(401)
      .json({ errorMessage: "비밀번호가 일치하지 않습니다." });
  }

  const token = jwt.sign(
    {
      userId: user.userId,
    },
    "Secret Key"
  );

  res.cookie("authorization", `Bearer ${token}`);
  return res.status(200).json({ message: "로그인 성공했습니다." });
});

 

 

사용자 인증 미들웨어

  • 클라이언트로 전달받은 쿠키를 검증하는 작업을 수행
  • 클라이언트가 제공한 쿠키에 담겨있는 JWT를 이용해 사용자를 조회

 

사용자 인증 미들웨어 비즈니스 로직

  1. 클라이언트로부터 쿠키를 전달받음
  2. 쿠키가 Bearer 토큰 형식인지 확인함
  3. 서버에서 발급한 JWT가 맞는지 검증함
  4. JWT의 userId를 이용해 사용자를 조회
  5. req.user에 조회된 사용자 정보를 할당
  6. 다음 미들웨어(next) 실행 

ex)

import jwt from "jsonwebtoken";
import { prisma } from "../utils/prisma/index.js";

export default async function (req, res, next) {
  try {
    const { authorization } = req.cookies;

    console.log(authorization);
    const [tokenType, token] = authorization.split(" ");
    console.log(tokenType);

    if (tokenType !== "Bearer")
      throw new Error("토큰 타입이 일치하지 않습니다.");

    const decodedToken = jwt.verify(token, "Secret Key");
    const userId = +decodedToken.userId;

    const user = await prisma.users.findFirst({
      where: { userId },
    });

    if (!user) throw new Error("토큰 사용자가 존재하지 않습니다.");

    req.user = user;

    next();
  } catch (error) {
    res.clearCookie("authorization");
    switch (error.name) {
      case "TokenExpiredError": // 토큰이 만료되었을 때 발생하는 에러
        return res.status(401).json({ errorMessage: "토큰이 만료되었습니다." });
      case "JsonWebTokenError": // 토큰 검증이 실패했을 때, 발생하는 에러
        return res
          .status(401)
          .json({ errorMessage: "토큰 인증에 실패하였습니다." });
      default:
        return res
          .status(401)
          .json({
            errorMessage: error.errorMessage ?? "비 정상적인 요청입니다.",
          });
    }
  }
}

 

이를 이용한 사용자 정보 조회

/* 사용자 조회 API */
router.get("/users", authMiddleware, async (req, res, next) => {
  const { userId } = +req.user;

  const user = await prisma.users.findFirst({
    where: { userId },
    select: {
      userId: true,
      email: true,
      createdAt: true,
      updatedAt: true,
      UserInfos: {
        select: {
          name: true,
          age: true,
          gender: true,
          profileImage: true,
        },
      },
    },
  });

  return res.status(200).json({ data: user });
});
  • 위와 같이 중첩 select 문법SQL의 JOIN과 동일한 역할을 수행함
  • 중첩 select 문법을 사용하기 위해선 Prisma model에서 @relation()과 같이 관계 설정이 되어야 함