[#66] 비밀번호 암호화 + JWT 토큰 발급 (feat. 로그인)

2025. 5. 1. 16:34·LG 유플러스 유레카 SW/React

bcryptjs

비밀번호를 안전하게 암호화(해시화) 하기 위해 사용하는 JavaScript 라이브러리
  • 비밀번호 해시 생성 (Hashing)
  • 비밀번호 검증 (Compare) – 입력된 평문 비밀번호와 저장된 해시가 일치하는지 확인
  • Salt 사용 – 동일한 비밀번호라도 매번 다른 해시 결과를 만듬 (보안 강화)

설치

npm i bcryptjs

회원가입 시, 비밀번호 암호화

import bcrypt from "bcryptjs";
const saltRounds = 10; // salt 길이

app.post("/register", async (req, res) => {
  const { userName, password } = req.body;
  // userModel 에서 이미 존재하는 사용자인지 확인
  const existingUser = await userModel.findOne({ userName });
  if (existingUser)
    return res.status(409).json({ error: "이미 존재하는 사용자입니다." });
  // 새 사용자 생성
  const userDoc = new userModel({
    userName,
    password: bcrypt.hashSync(password, saltRounds), // 비밀번호를 암호화(해시)
  });
  // DB에 저장
  const savedUser = await userDoc.save();
  // 저장 성공 시, 응답 메시지 전송
  res
    .status(200)
    .json({ user: { userName: savedUser.userName, _id: savedUser._id } });
});

로그인 시, 비밀번호 검증

app.post("/login", async (req, res) => {
  const { userName, password } = req.body;

  const userDoc = await userModel.findOne({ userName });
  if (!userDoc) return res.status(401).json({ error: "없는 사용자입니다." });

  // 비밀번호 확인(암호 해독)
  const passOk = bcrypt.compareSync(password, userDoc.password);
  if (!passOk) return res.status(401).json({ error: "비밀번호가 틀렸습니다." });
  else {};
}

 

JWT 토큰

웹에서 사용자 인증과 정보 전달을 위해 사용하는 토큰 기반 인증 방식
  • 로그인한 사용자임을 증명하거나, 서버 간 안전하게 데이터를 교환할 때 사용

설치

npm install jsonwebtoken

JWT 토큰 발급

import jwt from "jsonwebtoken";
// 환경 변수로 관리 필요
const secretKey = "test"; // JWT 서명에 사용할 비밀 키
const tokenLife = "1h"; // JWT의 만료 시간 설정

app.post("/login", async (req, res) => {
 	// ...중략
    else {
        // JWT 토큰 발급
        const { _id, userName } = userDoc;
        const payload = { id: _id, userName };
        const token = jwt.sign(payload, secretKey, {
          expiresIn: tokenLife,
        });
        // 쿠키에 저장
        res
          .cookie("token", token, {
            // 보안 설정
            httpOnly: true, // JavaScript에서 쿠키 접근 불가 (XSS 방지)
            secure: process.env.NODE_ENV === "production", // 환경에 따른 쿠키 보안 설정, 배포 환경에서만 true 설정
            sameSite: "Strict", // 동일 출처에서만 쿠키 전송 (CSRF 방지)
            maxAge: 1000 * 60 * 60, // 쿠키 만료 시간(ms)
          })
          .json({ user: { id: userDoc._id, userName } });
    }
}

 

헤더 로그인 상태 유지

1. 서버 - 로그인 성공 시 JWT 토큰을 HttpOnly 쿠키에 저장

res
  .cookie("token", token, {
    // 보안 설정
    httpOnly: true, // JavaScript에서 쿠키 접근 불가 (XSS 방지)
    secure: process.env.NODE_ENV === "production", // 환경에 따른 쿠키 보안 설정, 배포 환경에서만 true 설정
    sameSite: "Strict", // 동일 출처에서만 쿠키 전송 (CSRF 방지)
    maxAge: 1000 * 60 * 60, // 쿠키 만료 시간(ms)
  })
  .json({ user: { id: userDoc._id, userName } });

 

2. API 요청 시 브라우저가 자동으로 Authorization 헤더 없이 해당 쿠키를 요청에 포함

📍 CORS 설정 필요

  • 서버에서 credentials: true, origin 설정
app.use(cors({ credentials: true, origin: "http://localhost:5173" })); // 쿠키에 포함한 요청을 허용
  • 클라이언트에서 withCredentials: true를 설정
axios.defaults.withCredentials = true // 모든 요청에 대해 withCredentials 설정

 

3. 클라이언트 - 쿠키에 저장된 인증 정보를 기반으로 로그인된 사용자의 정보 요청

// userApi.js
export const getUserProfile = async () => {
  const res = await axios.get(`${API_URL}/profile`)
  return res.data
}

 

4. 서버 - 쿠키에서 JWT 토큰 추출

쿠키에 저장된 JWT 토큰을 cookie-parser 미들웨어를 사용하여 추출하고 인증 확인

  • 설치
npm i cookie-parser
  • 쿠키에서 JWT 토큰 추출
import cookieParser from "cookie-parser";
app.use(cookieParser()); // 쿠키 파싱을 위한 미들웨어 추가

// 회원 정보 조회
app.get("/profile", (req, res) => {
  const token = req.cookies.token; // 쿠키에서 JWT 토큰 추출
  if (!token) {
    return res.status(401).json({ error: "로그인이 필요합니다." });
  }
  // 토큰이 변조되었거나 만료되었는지 검사
  jwt.verify(token, secretKey, (err, info) => {
    if (err) return res.json({ error: "로그인이 필요합니다." });
    res.json(info); // 정상이라면 payload를 복호화해서 callback의 두 번째 인자로 넘겨줌
  });
});

 

5. 클라이언트 - 로그인한 사용자 정보 전역 상태 관리

  • Redux Toolkit을 사용해서 user 상태를 관리하는 slice 정의
// userSlice.js
import { createSlice } from '@reduxjs/toolkit'

export const user = createSlice({
  name: 'user',
  initialState: {
    user: '',
  },
  reducers: {
    setUserInfo: (state, action) => {
      state.user = action.payload
    },
  },
})

export const { setUserInfo } = user.actions
  • 로그인한 사용자 정보를 가져와 Redux에 저장하고, UI에 반영
// Header.jsx
export const Header = () => {
  const dispatch = useDispatch()
  const user = useSelector(state => state.user.user)
  const userName = user?.userName

  useEffect(() => {
    const getProfile = async () => {
      try {
        const userData = await getUserProfile()
        dispatch(setUserInfo(userData))
      } catch {
        dispatch(setUserInfo(''))
      }
    }
    getProfile()
  }, [dispatch, userName])

  return (
	// ... 생략
  )
}

 

로그아웃 처리

서버

app.post("/logout", (req, res) => {
  res
    .cookie("token", "", {
      httpOnly: true,
      expires: new Date(0), // 쿠키 만료 시간을 0으로 설정하여 삭제
    })
    .json({ message: "로그아웃 되었습니다." });
});

클라이언트

// userApi.js
export const logoutUser = async () => {
  const response = await axios.post(`${API_URL}/logout`)
  return response.data
}
// Header.jsx
const handleLogout = async () => {
    try {
      await logoutUser()
      dispatch(setUserInfo(''))
    } catch (err) {
      console.log(err)
    }
}

 

 

react-quill

React에서 WYSIWYG(What You See Is What You Get) 에디터를 구현할 때 사용하는 리치 텍스트 에디터 라이브러리. 즉, "글 작성기"를 만들어주는 도구

  • 글씨 크기, 색상, 볼드/이탤릭, 리스트 등 서식 있는 텍스트 작성 가능
  • 이미지, 링크, 코드블럭 등 포맷팅 요소 지원
  • HTML 형태로 데이터 저장 가능 ➡️ DB에 저장하거나 미리보기 가능
  • 커스터마이징 가능 (툴바, 테마 등)

  • react-quill-new는 기존 react-quill의 개선된 버전으로, React 19와 함께 사용할 수 있도록 호환성 문제를 해결한 패키지
npm install react-quill-new

 

dotenv

Node.js 환경에서 환경 변수를 .env 파일에 정의하고, 이를 process.env를 통해 사용할 수 있도록 도와주는 환경 변수 로딩 라이브러리

npm install dotenv
// index.js
import dotenv from "dotenv";

dotenv.config();

const port = process.env.PORT || 4000; // 포트 번호
const saltRounds = Number(process.env.SALT_ROUND); // salt 길이
const secretKey = process.env.JWT_SCR_KEY; // JWT 서명에 사용할 비밀 키
const tokenLife = process.env.SALT_ROUND; // JWT의 만료 시간 설정
728x90
반응형

'LG 유플러스 유레카 SW > React' 카테고리의 다른 글

[#67] Multer 파일 업로드 + 게시글 작성/조회  (1) 2025.05.02
[#65] React + Express + MongoDB 연동하기 (feat. 회원가입)  (3) 2025.04.30
[#64] TanStack Query(React Query) 사용해보기  (0) 2025.04.29
[#63] 날씨 오픈 API 연동  (1) 2025.04.28
[#62] 용돈 기입장 프로젝트 (feat. React)  (0) 2025.04.28
'LG 유플러스 유레카 SW/React' 카테고리의 다른 글
  • [#67] Multer 파일 업로드 + 게시글 작성/조회
  • [#65] React + Express + MongoDB 연동하기 (feat. 회원가입)
  • [#64] TanStack Query(React Query) 사용해보기
  • [#63] 날씨 오픈 API 연동
nueos
nueos
  • nueos
    nueos 공부 기록
    nueos
  • 전체
    오늘
    어제
    • 분류 전체보기 (190)
      • 해커톤 (1)
      • 네이버 BoostCamp (6)
      • LG 유플러스 유레카 SW (83)
        • React (21)
        • TypeScript (2)
        • JavaScript (2)
        • HTML+CSS (5)
        • Spring (7)
        • Java (6)
        • SQL (2)
        • Algorithm (8)
        • CX (6)
        • Git (2)
        • 프로젝트 (2)
        • 스터디 (9)
        • 과제 (8)
        • 특강 (1)
      • React (3)
      • Next (0)
      • Javascript (2)
      • HTML (2)
      • CSS (9)
      • Algorithm (6)
      • Database (0)
      • OS (13)
      • C++ (24)
      • Python (1)
      • jQuery (1)
      • Django (1)
      • Git (1)
      • 개발 지식 (3)
      • 정보 보안 (22)
      • 포렌식 (1)
      • 암호 (2)
      • 기타 (4)
      • 패스트캠퍼스 FE 프로젝트십 (5)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    제주지역혁신플랫폼지능형서비스사업단
    Stack
    Queue
    기술로바꾸는세상
    제주해커톤
    heap
    완전 탐색
    스택
    디지털혁신
    큐
    디지랩챌린지
    exhaustive search
    힙
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.2
nueos
[#66] 비밀번호 암호화 + JWT 토큰 발급 (feat. 로그인)
상단으로

티스토리툴바