TIL

내일배움캠프 5주차 수요일 TIL

news0516 2024. 11. 27. 21:34
// (?=.*\d) : 최소한 1개 이상의 숫자가 포함되어야 합니다
// [a-z0-9]+ : 소문자와 숫자로만 이루어져야 합니다
// ^와 $ : 문자열의 시작과 끝을 의미합니다.
const idForm = /^(?=.*[a-z])(?=.*\d)[a-z0-9]+$/;
해당 코드로 수정하여 영어(소문자) + 숫자 가 포함된 형식으로 아이디를 생성하도록 적용하였다.

 

// 로그인 API

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

const router = express.Router();

router.post("/sign-in", async (req, res) => {
  const { id, password } = req.body;
  const accounts = await prisma.accounts.findFirst({ where: { id } });

  if (!accounts)
    return res.status(401).json({ message: "존재하지 않는 id입니다." });
  // 입력받은 사용자의 비밀번호와 데이터베이스에 저장된 비밀번호를 비교합니다.
  else if (!(await bcrypt.compare(password, accounts.password)))
    return res.status(401).json({ message: "비밀번호가 일치하지 않습니다." });

  // 로그인에 성공하면, 사용자의 accout_id를 바탕으로 토큰을 생성합니다.
  const token = jwt.sign(
    {
      account_id: accounts.account_id,
    },
    // JWT를 서명하는 데 사용되는 비밀 키
    // 서버가 비밀 키를 사용하여 토큰 변조 여부를 알 수 있다
    "server-secret-key",
    { expiresIn: "1h" }
  );
  return res.status(200).json({ message: "로그인 성공", token });
});
export default router;


추가로 로그인 API에서 로그인 성공 시 토큰을 생성하도록 업데이트하였다.

// 인증 미들웨어 auth.js

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

export default async function authM(req, res, next) {
  try {
    // 클라이언트로부터 요청이 들어오면 req.headers에서 authorization 헤더를 추출
    const { authorization } = req.headers;
    console.log(authorization);
    if (!authorization) throw new Error("토큰이 존재하지 않습니다.");

    // authorization 헤더의 값을 공백으로 나누어 tokenType과 token을 추출
    const [tokenType, token] = authorization.split(" ");
	
    // tokenType이 "Bearer"인지 검증
    if (tokenType !== "Bearer")
      throw new Error("토큰 타입이 일치하지 않습니다.");
	
	// jwt.verify 함수를 사용하여 token을 검증
    // 비밀 키 "server-secret-key"를 사용하여 토큰의 유효성을 확인
    const decodedToken = jwt.verify(token, "server-secret-key");
    const accountsId = decodedToken.account_id;
	
	// prisma.accounts.findFirst를 사용하여 db에 account_id가 존재하는지 확인
    const accounts = await prisma.accounts.findFirst({
      where: { account_id: accountsId },
    });
    if (!accounts) {
      throw new Error("토큰 사용자가 존재하지 않습니다.");
    }

    // req.accounts 사용자 정보를 저장합니다.
    req.abc = accounts;
	// 객체 확인
    console.log("인증된 정보 :", req.abc);
    next();
  } catch (error) {
    // 토큰이 만료되었거나, 조작되었을 때, 에러 메시지를 다르게 출력합니다.
    switch (error.name) {
      case "TokenExpiredError":
        return res.status(401).json({ message: "토큰이 만료되었습니다." });
      case "JsonWebTokenError":
        return res.status(401).json({ message: "토큰이 조작되었습니다." });
      default:
        return res
          .status(401)
          .json({ message: error.message ?? "비정상적인 요청입니다." });
    }
  }
}


이후 미들웨어 폴더 생성 > 인증 미들웨어를 구현하였다. 로그인 성공 시 생성되는 토큰을 바탕으로 인증을 진행하고, 인증 성공 시 req.abc 객체에 인증된 사용자 정보가 저장된다.

import express from "express";
import { prisma } from "../utils/prisma/index.js";
import authM from "../middlewares/auth.js";

// character.js
const router = express.Router();

// create-character 경로에 대한 POST 요청을 처리하는 API 구성
// authM 미들웨어를 사용하여 인증을 수행
router.post("/create-character", authM, async (req, res) => {
  // 요청 본문에서 nickname 추출
  const { nickname } = req.body;
  // authM 미들웨어에서 인증을 거친 accounts 정보를 가져오고
  // accounts에서 account_id를 추출한다
  const { account_id } = req.abc;

  // 닉네임 중복 검증
  const isExistCharacter = await prisma.characters.findFirst({
    where: { nickname },
  });

  if (isExistCharacter) {
    return res.status(409).json({ message: "이미 존재하는 닉네임입니다." });
  }

  // 캐릭터 생성 로직
  const newCharacter = await prisma.characters.create({
    data: {
      // 뽑아온 nickname, account_id를 각 컬럼에 적용한다.
      account_id: account_id,
      nickname: nickname,
      health: 500,
      power: 100,
      money: 10000,
    },
  });
  return res
    .status(201)
    .json({ message: "캐릭터 생성 성공", character: newCharacter });
});

export default router;


인증 미들웨어에서 저장된 계정 정보를 토대로, 캐릭터 테이블에 정보를 적용하여 캐릭터를 생성한다.

인증된 계정에 입력한 닉네임과 기본 스탯이 적용된 캐릭터 생성 완료


이 과정 중  미들웨어 적용에 대한 문제를 겪었다. 처음에는 app.js 파일에서 전역적으로 미들웨어를 적용하려고 했고, 이 방법은 모든 라우터에 미들웨어가 적용되기 때문에 인증이 필요하지 않은 요청에도 인증과정이 거쳐지는 문제였다.
그래서 튜터님을 통해 라우터에 개별적으로 미들웨어를 적용하는 방법을 배웠고, 이를 통해 각 라우터의 요구 사항에 맞게 미들웨어를 선택적으로 적용할 수 있게 되었다. 
나의  경우에는 미들웨어를 전역적으로 적용하는 것보다 라우터에 개별적으로 적용하는 것이 더 효과적이였다.