Programmers

[과제]Express Book Market API: JWT 인증 기능 테스트 시나리오

PARKpatchnotes 2025. 10. 29. 08:41

 

시나리오 1: 정상적인 인증 흐름 (Happy Path)

사용자가 회원가입 후 로그인하여 발급받은 accessToken으로 보호된 API에 성공적으로 접근하는 가장 기본적인 흐름입니다.

실행 간단 흐름도

  1. 클라이언트 → 서버 (회원가입): POST /users/register
  2. 클라이언트 → 서버 (로그인): POST /users/loginaccessToken, refreshToken 수신
  3. 클라이언트 → 서버 (장바구니 추가): POST /carts (헤더에 accessToken 포함)
    • 미들웨어 (authenticateJWT): 토큰 검증 성공 → req.user에 사용자 정보 저장 후 next()
    • Controller: req.user.id를 사용하여 장바구니 추가 로직 실행
    • Service & Repository: DB에 장바구니 데이터 삽입
    • Controller: 201 Created 응답 반환

1단계: 사용자 회원가입 및 로그인 (Controller & Service)

사용자가 emailpassword를 제공하여 회원가입하고, 동일한 정보로 로그인하여 accessTokenrefreshToken을 발급받습니다.

// [1-1] 회원가입 요청
POST /users/register
Content-Type: application/json

{
  "email": "testuser@example.com",
  "password": "password123"
}

// [1-2] 로그인 요청
POST /users/login
Content-Type: application/json

{
  "email": "testuser@example.com",
  "password": "password123"
}
  • 예상 결과 (1-1): 201 Created / {"message": "회원가입이 완료되었습니다."}

  • 예상 결과 (1-2): 200 OK / {"accessToken": "...", "refreshToken": "..."}


2단계: 보호된 API 접근 (Middleware & Controller)

발급받은 accessTokenAuthorization 헤더에 담아 장바구니 추가 API를 호출합니다.

// [1-3] 장바구니 추가 요청
POST /carts
Authorization: Bearer <1-2에서 받은 accessToken>
Content-Type: application/json

{
  "book_id": 1,
  "quantity": 1
}
  • authenticateJWT 미들웨어는 토큰을 성공적으로 검증하고, req.user에 디코딩된 사용자 정보를 저장합니다.
  • cart.controller.jsaddToCart 함수는 req.user.id를 참조하여 어떤 사용자의 요청인지 식별합니다.

3단계: 비즈니스 로직 처리 및 응답 (Service & Repository)

cartServicecartRepository는 전달받은 userIdbook_id를 사용하여 데이터베이스에 장바구니 항목을 추가(UPSERT)합니다.

// cart.repository.js
exports.upsertCartItem = ({ userId, book_id, quantity }) => {
  const sql = `
    INSERT INTO carts (user_id, book_id, quantity) VALUES (?, ?, ?)
    ON DUPLICATE KEY UPDATE quantity = quantity + VALUES(quantity)`;
  return dbPool.query(sql, [userId, book_id, quantity, quantity]);
};

4단계: API 최종 결과

  • 예상 결과 (1-3): 201 Created / {"message": "장바구니에 상품을 담았습니다."}


시나리오 2: 만료된 Access Token으로 API 접근

토큰이 만료되었을 때 서버가 어떻게 반응하는지 검증하는 흐름입니다.

실행 간단 흐름도

  1. 클라이언트 → 서버 (로그인): POST /users/login → 만료 시간이 짧은 accessToken 수신
  2. (시간 경과): accessToken 만료
  3. 클라이언트 → 서버 (보호된 API 요청): GET /carts (헤더에 만료된 accessToken 포함)
    • 미들웨어 (authenticateJWT): jwt.verify() 실행 시 TokenExpiredError 발생
    • catch 블록 실행: CustomError 생성 후 next(err) 호출
    • 에러 핸들러: 403 Forbidden 상태 코드와 에러 메시지 응답

1단계: 만료된 토큰 생성 및 API 요청

사전 준비: toker.utils.jsgenerateAccessToken 함수에서 expiresIn 값을 '1s'로 변경합니다.

// [4-1 & 4-2] 로그인 후 1초 이상 대기
POST /users/login
...

// [4-3] 만료된 토큰으로 장바구니 조회 요청
GET /carts
Authorization: Bearer <만료된 accessToken>

만료된 토큰으로 장바구니 조회 요청

 


2단계: 에러 처리 (Middleware)

authenticateJWT 미들웨어의 try-catch 구문이 핵심적인 역할을 합니다. jwt.verify()TokenExpiredError를 던지면 catch 블록이 이를 잡아냅니다.

// authorize.middleware.js
try {
  // jwt.verify()에서 TokenExpiredError 발생
  const decoded = jwt.verify(token, process.env.ACCESS_SECRET_KEY);
  // ...
} catch (error) {
  // error는 TokenExpiredError 객체
  return next(
    new CustomError(FORBIDDEN.statusCode, "Invalid or expired token.")
  );
}

3단계: API 최종 결과

  • 예상 결과 (4-3): 403 Forbidden / {"message": "Invalid or expired token."}

장바구니 추가 Invalid or expired token 오류


결론

  • authenticateJWT는 토큰의 유무와 유효성을 엄격하게 검사하여, 인증 실패 시 에러 핸들링 플로우를 통해 일관된 실패 응답을 보장하는 '게이트키퍼' 역할을 한다.
  • 계층형 아키텍처와 중앙 집중식 에러 핸들링 덕분에, 인증 로직이 비즈니스 로직과 명확히 분리되어 유지보수가 용이하다.