요즘은 Bestia의 프론트엔드 개발자로서 다양한 기능들을 개발중입니다.
유저 기능을 개발하던 중, 유저 인증 및 보안과 관련된 이슈를 챙기기 위한 인증 방식으로 JWT를 선택하였고, 이를 클라이언트-백엔드 간 전달하는 방식을 header와 httpOnly 쿠키로 결정하였습니다.
# 목차
1. JWT란?
2. JWT를 어떻게 사용하나요?
3. 실제 사용 예시
# JWT란?
JSON Web Token의 줄임말입니다.
서버와 클라이언트 사이에서 인증 정보를 주고받을 때 사용하는 토큰 기반 인증 방식입니다.
여기서 토큰이란,
증명서 역할을 하는 문자열인데요,
어떤 사용자가 누구인지, 어떤 권한이 있는지를 담고, 서버가 신뢰할 수 있게 서명해둔 형태입니다.
JWT는 이 토큰을 JSON 형식으로 구성하고, Base64로 인코딩한 문자열입니다.
JWT의 구조를 살펴보자면,
Header | 어떤 알고리즘으로 서명했는지 (예: HS256, RS256 등) |
Payload | 실제 데이터, 즉 유저 정보 (email, userId, role 등) |
Signature | Header와 Payload를 비밀 키로 사용해 서명한 값 (위조 방지용) |
이렇게 3가지 파트로 이뤄져있는데, "."으로 구분합니다.
실제 예시와 함께 살펴봅시다.
아래는 GPT를 통해 생성한 Access Token입니다.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjM0NSIsInVzZXJuYW1lIjoiam9obmRvZSIsImVtYWlsIjoiam9obkBleGFtcGxlLmNvbSIsImlhdCI6MTcwODAwMDAwMCwiZXhwIjoxNzA4MDAzNjAwfQ.5mVUeqqS9w2VOfMqtXkcvYeACrAh_XcIAG8QqEDUP3I
이를 "."을 기준으로 쪼개보면,
1. Header: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
2. Payload: eyJ1c2VySWQiOiIxMjM0NSIsInVzZXJuYW1lIjoiam9obmRvZSIsImVtYWlsIjoiam9obkBleGFtcGxlLmNvbSIsImlhdCI6MTcwODAwMDAwMCwiZXhwIjoxNzA4MDAzNjAwfQ
3. Signature: 5mVUeqqS9w2VOfMqtXkcvYeACrAh_XcIAG8QqEDUP3I
아래와 같은 결과가 나옵니다.
위 내용을 Base64를 통해 다시 디코딩하면,
1. Header
Header에는 어떤 서명 알고리즘을 사용해서 Signature를 만들었는지와 토큰이 어떤 타입인지에 대해 나타납니다.
{
"alg": "HS256", // HMAC-SHA256 알고리즘 사용
"typ": "JWT" // JWT 사용
}
2. Payload
여긴 유저의 정보가 나타납니다.
{
"userId": "12345", // 사용자 고유 ID
"username": "johndoe", // 사용자 이름
"email": "john@example.com", // 사용자 이메일
"iat": 1708000000, // 발급 시간 (Issued At) → UTC 기준 초 단위 (epoch time)
"exp": 1708003600 // 만료 시간 (Expires At) → 1시간 뒤
}
3. Signature
Header와 Payload를 비밀 키로 HMAC-SHA256 방식으로 서명한 값입니다.
서버는 이 서명을 확인해 위조 방지를 수행합니다.
토큰이 위조되지 않았는지 검증하고, 서명이 다르다면 Header나 Payload가 위조되었다 판단하고 인증을 거부합니다.
5mVUeqqS9w2VOfMqtXkcvYeACrAh_XcIAG8QqEDUP3I
# JWT는 언제 사용하나요?
위에서 언급하였듯, 서버와 클라이언트 사이에서 인증 정보를 주고받을 때 사용합니다.
더 구체적으로 이야기 해보자면,
- 로그인 후 인증 상태 유지 (세션 대신)
- API 서버에서 사용자 인증 처리
- 권한 체크(예: 관리자만 접근 가능한 페이지)
이런 상황들이 있습니다.
# JWT를 어떻게 사용하나요?
JWT를 통해 우리가 '인증/인가'를 다룰 때에는 주로 Access Token과 Refresh Token을 만들어 사용합니다.
- 왜?
보안적인 측면이 큽니다.
Access Token은 짧은 시간만 유효하기 때문에 유출되더라도 피해를 최소화할 수 있고,
Refresh Token은 백그라운드에서 새로운 Access Token을 발급받기 위해 사용됩니다.
항목 | Access Token | Refresh Token |
용도 | 인증된 사용자임을 증명 | 새로운 Access Token 발급 요청 |
수명 | 짧음 (보통 5~15분) | 김 (보통 7일~30일) |
사용 위치 | Authorization 헤더에 포함 (Bearer) | 주로 HttpOnly 쿠키에 저장 |
보안 사고 시 영향 | API 직접 접근 가능 → 위험 | Access Token 계속 재발급 가능 → 더 위험 |
서버 저장 필요 여부 | ❌ (Stateless) | ✅ (DB, Redis 등으로 관리하는 것이 일반적) |
재발급 가능 여부 | 불가능 | 가능 (Access Token 재발급용) |
로그아웃 시 조치 | 클라이언트에서 삭제 | 서버에서 블랙리스트 처리 or DB에서 제거 필요 |
# 실제 사용 예시
저희 서비스에서는 어떻게 동작하는지 알아봅시다.
1. 로그인 시
- 클라이언트가 아이디/비밀번호로 로그인 요청
- 서버에서 사용자 인증 성공 시:
- Access Token은 Authorization 헤더로, Refresh Token은 HttpOnly 쿠키를 통해 전달
- Refresh Token은 HttpOnly 쿠키에 저장
2. API 요청 시
- 클라이언트는 Authorization: Bearer <Access Token> 헤더로 API 요청
- 서버는 Access Token을 검증하고 요청을 처리
- Access Token이 유효하면 → 요청 정상 처리
- Access Token이 만료되었으면 → 아래로
3. Access Token 만료 시 (자동 재발급)
- 클라이언트가 자동으로 Refresh Token을 사용해 /api/auth/refresh-token에 요청
- 서버는 Refresh Token을 검증하고:
- 유효하다면 새로운 Access Token 재발급
- 선택적으로 Refresh Token도 재발급 (Rotation 적용 시)
- 클라이언트는 새 Access Token으로 API 요청 재시도
4. 로그아웃 시
- 클라이언트:
- 저장된 Access Token 제거
- 서버:
- Refresh Token을 DB/Redis에서 삭제
이렇게 저희 인증 방식이 진행되고 있습니다.
한동안 백엔드 친구와 Token을 어떻게 보내는지에 대해 이야기를 나눴었는데, 이번 경험으로 JWT에 대해 더 자세히 알아보았던 것 같습니다.
틀리거나 더 나은 방법이 있다면 언제든 피드백 주시면 감사드리겠습니다!