← 블로그 목록

JWT 토큰 완벽 가이드: 현대적 인증 방식 이해하기

JWT(JSON Web Token)의 원리, 구조, 보안, 실제 구현까지. 토큰 기반 인증을 완벽히 이해하세요.

JWT, 모던 웹의 표준 인증 방식

JWT(JSON Web Token)는 사용자 인증 정보를 안전하게 전달하는 방식입니다. 세션 기반 인증의 한계를 극복하고, 마이크로서비스, 모바일 앱, API 서버 간 통신을 가능하게 합니다.

Google, GitHub, Auth0 등 주요 서비스들이 모두 JWT를 사용합니다. 이 가이드에서는 JWT의 모든 것을 배웁니다.

1부: JWT vs 세션 기반 인증

세션 기반 인증의 한계

클라이언트 → 로그인 → 서버 (세션 저장)
           ← 세션 ID 쿠키

문제:
1. 서버가 모든 세션 데이터 저장 필요
2. 서버 확장 시 세션 동기화 복잡
3. 도메인 간 공유 어려움
4. 모바일 앱에서 쿠키 사용 어려움

JWT 기반 인증의 장점

클라이언트 → 로그인 → 서버 (검증)
           ← JWT 토큰

장점:
1. 서버는 토큰 검증만 함 (저장 불필요)
2. 무상태(Stateless) - 서버 확장 용이
3. 도메인/도메인 간 공유 가능
4. 모바일, SPA에 적합

2부: JWT 구조

JWT의 3부 구조

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

구조: Header.Payload.Signature

1. Header (헤더)

Base64 디코딩하면:
{
  "alg": "HS256",   // 서명 알고리즘
  "typ": "JWT"      // 토큰 타입
}

2. Payload (페이로드)

Base64 디코딩하면:
{
  "sub": "1234567890",     // Subject (사용자 ID)
  "name": "John Doe",      // 사용자 이름
  "iat": 1516239022,       // Issued At (발급 시간)
  "exp": 1516242622        // Expiration (만료 시간)
}

표준 Claims

  • iss: Issuer (발급자)
  • sub: Subject (주체, 보통 사용자 ID)
  • aud: Audience (대상)
  • exp: Expiration Time (만료 시간, Unix timestamp)
  • iat: Issued At (발급 시간)
  • nbf: Not Before (유효 시작 시간)

3. Signature (서명)

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)

용도:
- 토큰이 변조되지 않았음을 보장
- 발급자가 신뢰할 수 있음을 증명

3부: JWT 만드는 과정

Step 1: Header 생성

const header = {
  alg: "HS256",
  typ: "JWT"
};

const encodedHeader = base64url(JSON.stringify(header));

Step 2: Payload 생성

const payload = {
  sub: "user123",
  name: "John Doe",
  email: "john@example.com",
  iat: Math.floor(Date.now() / 1000),
  exp: Math.floor(Date.now() / 1000) + (60 * 60 * 24) // 1일 후
};

const encodedPayload = base64url(JSON.stringify(payload));

Step 3: 서명 생성

const secret = "your-secret-key";

const signature = HMACSHA256(
  encodedHeader + "." + encodedPayload,
  secret
);

const encodedSignature = base64url(signature);

Step 4: 최종 JWT

const jwt = encodedHeader + "." + encodedPayload + "." + encodedSignature;

4부: JWT 검증

토큰 검증 절차

클라이언트가 보낸 JWT 수신

Step 1: 형식 확인
→ Header.Payload.Signature 형식인가?

Step 2: Signature 재검증
→ 저장된 secret으로 서명 재계산
→ 보낸 서명과 일치하는가?

Step 3: Payload 검증
→ 만료 시간(exp) 확인
→ 필수 정보 확인

검증 성공 → 토큰 신뢰 가능

JavaScript 예시

function verifyJWT(token, secret) {
  const parts = token.split('.');
  if (parts.length !== 3) return false;

  const [headerEnc, payloadEnc, signatureEnc] = parts;

  // 서명 재검증
  const expectedSignature = HMACSHA256(
    headerEnc + "." + payloadEnc,
    secret
  );

  if (signatureEnc !== base64url(expectedSignature)) {
    return false; // 변조됨
  }

  // Payload 디코딩
  const payload = JSON.parse(base64urlDecode(payloadEnc));

  // 만료 시간 확인
  if (payload.exp < Math.floor(Date.now() / 1000)) {
    return false; // 만료됨
  }

  return payload;
}

5부: JWT 보안

1. Secret Key 관리

❌ 하드코딩
const secret = "mysecretkey";

✅ 환경 변수
const secret = process.env.JWT_SECRET;

2. 토큰 저장 위치

❌ localStorage (XSS 공격에 취약)
localStorage.setItem('token', jwt);

✅ HttpOnly 쿠키 (XSS 방지)
res.cookie('token', jwt, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict'
});

3. 만료 시간 설정

❌ 무제한 토큰
exp: null

✅ 짧은 만료 시간
exp: Math.floor(Date.now() / 1000) + (15 * 60)  // 15분

4. Refresh Token

Access Token (15분):
- 짧은 만료 시간
- API 요청에 사용

Refresh Token (7일):
- 긴 만료 시간
- 새 Access Token 발급에만 사용
- 더 안전하게 저장

장점: 공격자가 Access Token 탈취해도 제한적 피해

6부: JWT 실제 활용

로그인 플로우

1. 클라이언트: 이메일/비밀번호 전송
2. 서버: 검증 후 JWT 발급
   - Access Token
   - Refresh Token
3. 클라이언트: 토큰 저장 (쿠키 또는 메모리)
4. API 요청: Authorization 헤더에 토큰 포함
   GET /api/profile
   Authorization: Bearer eyJhbGc...

API 서버에서 검증

function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];

  if (!token) return res.sendStatus(401);

  jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) return res.sendStatus(403);
    req.user = user;
    next();
  });
}

app.get('/api/profile', authenticateToken, (req, res) => {
  res.json({ user: req.user });
});

7부: JWT 디코더로 확인

JWT 디코더를 사용하면:

  • Header, Payload, Signature 각각 확인
  • 만료 시간 확인
  • Payload의 정보 확인
  • 서명 검증 (secret 필요)

자주 하는 실수

1. Secret을 공개 저장소에 노출

❌ GitHub에 secret 업로드

✅ .env 파일 (.gitignore에 포함)

2. 만료 시간 미설정

❌ 토큰에 만료 시간이 없음

✅ 반드시 exp 설정

3. HTTP로 토큰 전송

❌ http://example.com으로 토큰 전송 (MITM 공격)

✅ HTTPS 사용 필수

4. 너무 많은 정보 포함

❌ 토큰에 모든 사용자 정보 포함 (토큰 크기 증가)

✅ 필수 정보만 포함 (ID, 역할 등)

마무리

JWT는 현대 웹 개발의 필수 요소입니다. 올바르게 구현하면 안전하고 확장 가능한 인증 시스템을 만들 수 있습니다.

JWT 디코더로 토큰을 분석해보고, 보안 가이드를 참고하여 안전한 인증을 구현하세요!