JWT(JSON Web Token) 완벽 가이드: 실전 경험을 바탕으로

oversleep·2025년 2월 17일
0

Web

목록 보기
8/11
post-thumbnail

들어가며

React Native로 앱을 개발하던 중 흥미로운 상황에 직면했습니다.
매칭 목록 화면에서 사용자가 자신이 만든 매칭을 구분할 수 있도록 하는 기능을 구현하고 있었는데, 각 매칭의 hostId와 현재 로그인한 사용자의 ID를 비교해야 했습니다.

문제는 로그인 API 응답에서 사용자 ID를 직접 받지 못하고 있었죠.
응답으로는 jti, accessToken, refreshToken만 받고 있어서 현재 사용자의 ID를 알 수 없었습니다.
이 문제를 해결하기 위해 백엔드 개발자와 논의하던 중, JWT(JSON Web Token)에 대해 자세히 알게 되었습니다.

JWT란?

JWT는 당사자 간에 정보를 JSON 객체로 안전하게 전송하기 위한 독립적인 방식입니다.
이 정보는 디지털 서명이 되어 있기 때문에 신뢰할 수 있습니다.

JWT의 구조

JWT는 세 부분으로 구성됩니다:

  1. Header (헤더)

    {
      "alg": "HS256",  // 서명 알고리즘
      "typ": "JWT"     // 토큰 유형
    }
  2. Payload (내용)

    {
      "sub": "user@email.com",  // 사용자 이메일
      "userId": 123,            // 사용자 ID
      "name": "John Doe",       // 사용자 이름
      "iat": 1516239022,        // 발행 시간
      "exp": 1516242622        // 만료 시간
    }
  3. Signature (서명)

    • 헤더와 페이로드를 Base64URL로 인코딩하고, 비밀 키를 사용하여 생성된 서명

이 세 부분은 각각 Base64로 인코딩되어 점(.)으로 구분됩니다:

xxxxx.yyyyy.zzzzz
Header.Payload.Signature

JWT의 주요 클레임(Claims)

Payload에 포함되는 주요 클레임들의 의미를 살펴보겠습니다:

  1. jti (JWT ID)

    • JWT의 고유 식별자
    • 토큰 재사용 방지와 보안 강화에 사용
    • UUID 형식으로 생성되는 것이 일반적
    • 사용 사례:
      • 여러 기기에서의 로그인 세션 구분
      • 특정 토큰 무효화(블랙리스트)
      • 토큰 사용 이력 추적
  2. iat (Issued At)

    • 토큰이 발급된 시간
    • Unix timestamp 형식
  3. exp (Expiration Time)

    • 토큰의 만료 시간
    • 보안을 위해 적절한 만료 시간 설정 중요

JWT의 작동 방식

  1. 사용자가 로그인을 성공하면 서버는 JWT를 생성합니다.

  2. 서버는 비밀 키를 사용하여 토큰에 서명합니다.

  3. 서버는 이 토큰을 클라이언트에게 전달합니다.

  4. 클라이언트는 이후의 요청에서 이 토큰을 헤더에 포함시켜 전송합니다.

  5. 서버는 토큰의 서명을 확인하고 요청을 처리합니다.

  6. 로그인 과정

   // 서버 응답 예시
   {
     jti: "1cc00dd7-75a3-41c6-9d8f-1aa150821d6b",
     accessToken: "eyJhbG...",
     refreshToken: "eyJhbG..."
   }
  1. 토큰 저장
   await AsyncStorage.multiSet([
     ["jti", response.data.jti],
     ["accessToken", response.data.accessToken],
     ["refreshToken", response.data.refreshToken],
     ["isLoggedIn", "true"],
   ]);
  1. 토큰 디코딩
   const decodeToken = (token: string) => {
     try {
       const base64Url = token.split('.')[1];
       const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
       const jsonPayload = decodeURIComponent(atob(base64).split('').map(c => 
         '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
       ).join(''));

       return JSON.parse(jsonPayload);
     } catch (error) {
       console.error('Error decoding token:', error);
       return null;
     }
   };

JWT의 장점

  1. 상태 비저장(Stateless)

    • 서버가 클라이언트의 상태를 저장할 필요가 없음
    • 서버 확장성 향상
  2. 자가 수용적(Self-contained)

    • 필요한 모든 정보를 토큰 자체에 포함
    • 추가 데이터베이스 조회 최소화
  3. 보안성

    • 디지털 서명으로 위조 및 변조 방지
    • 민감한 정보를 안전하게 전송 가능
  4. 확장성

    • 필요한 추가 정보를 payload에 포함 가능
    • 유연한 데이터 전송 구조

실제 사용 예시

userId를 JWT payload에 포함시켜 전송하기로 결정했습니다:

// 사용 예시
const getUserId = async () => {
  const token = await AsyncStorage.getItem('accessToken');
  if (!token) return null;
  
  const decoded = decodeToken(token);
  return decoded?.userId;
};

주의사항 및 Best Practices

  1. 토큰 관리와 저장
  • accessToken과 refreshToken의 적절한 만료 시간 설정
  • 클라이언트에서 안전한 저장소 사용 (AsyncStorage 등)
  • jti를 활용한 토큰 무효화 전략 수립
  • Refresh Token 구현 고려
  1. 보안
  • 중요한 개인정보나 민감한 정보는 Payload에 포함하지 않기
  • HTTPS 사용 필수
  • 토큰 탈취에 대비한 대응 방안 마련
  • 다중 로그인 상황에서의 토큰 관리 전략 수립
  1. 성능과 크기 최적화
  • Payload에 필요한 정보만 포함
  • 불필요하게 큰 토큰은 네트워크 성능에 영향
  • 토큰 크기가 커질수록 요청 처리 시간 증가
  • Base64 인코딩으로 인한 크기 증가 고려

여기서 추가로 설명드리자면:

accessToken은 보통 짧은 만료 시간(예: 1시간)
refreshToken은 긴 만료 시간(예: 2주)
토큰 크기는 일반적으로 1KB 이하로 유지하는 것이 좋음
민감한 정보의 예: 비밀번호, 개인식별정보, 금융정보 등

마치며

JWT는 단순히 인증을 위한 도구를 넘어 클라이언트-서버 간 안전한 정보 전달의 수단이 될 수 있습니다.
이번 경험이 좋은 예시인데, 처음에는 단순히 사용자 ID를 API 응답에 추가해달라고 요청하려 했지만, JWT의 payload를 활용하는 방식을 선택함으로써 더 깔끔하고 확장 가능한 솔루션을 구현할 수 있었습니다.

특히 JWT의 다양한 구성 요소들이 각자의 역할을 수행하는 것이 인상적이었습니다.
payload를 통한 데이터 전달뿐만 아니라, jti를 통한 세션 관리, 만료 시간을 통한 보안 강화 등 각 요소들이 서로 조화롭게 동작하면서 안전하고 효율적인 인증 시스템을 구축할 수 있었죠.

이러한 경험을 통해 프론트엔드 개발자로서 인증 시스템에 대한 이해도를 한층 높일 수 있었고, 백엔드 개발자와의 협업 시 더 나은 의사소통을 할 수 있게 되었습니다.

때로는 당면한 문제를 해결하기 위해 새로운 기술을 배우는 과정이, 결과적으로 더 나은 해결책을 찾는 길이 될 수 있다는 것을 깨달은 소중한 경험이었습니다.

profile
궁금한 것, 했던 것, 시행착오 그리고 기억하고 싶은 것들을 기록합니다.

0개의 댓글