안녕하세요! 오늘은 현대 웹 서비스에서 널리 사용되는 인증 방식인 JWT 토큰 인증에 대해 알아보겠습니다. 휴대폰 앱이나 웹사이트를 사용할 때 로그인을 하면 이후 페이지를 이동해도 계속 로그인 상태가 유지되는 것, 어떻게 가능한 걸까요? 🤔 바로 JWT와 같은 토큰 기반 인증 시스템이 그 비밀입니다!
등장 배경
초기 웹 서비스에서는 사용자 인증을 위해 세션 기반 인증을 주로 사용했습니다. 사용자가 로그인하면 서버에 세션을 생성하고, 클라이언트에는 세션 ID만 쿠키로 전달하는 방식이었죠. 하지만 이 방식은 몇 가지 문제점이 있었습니다:
[세션 기반 인증의 문제점]:
- 서버 부하 증가: 많은 사용자의 세션 정보를 서버에 저장해야 함
- 확장성 제한: 서버가 여러 대일 경우 세션 정보 공유의 어려움
- CORS(Cross-Origin Resource Sharing): 다른 도메인 간 인증 공유의 어려움
이러한 문제를 해결하기 위해 등장한 것이 바로 JWT(JSON Web Token) 입니다! JWT는 필요한 모든 정보를 토큰 자체에 포함시켜 서버에 상태를 저장할 필요가 없는 Stateless 인증 방식을 제공합니다. 😎
핵심 원리
JWT의 구조
JWT는 점(.)으로 구분된 세 부분으로 구성됩니다:
xxxxx.yyyyy.zzzzz
(헤더).(페이로드).(서명)
헤더(Header) 👒
{ "alg": "HS256", "typ": "JWT" }
alg
: 서명 알고리즘 (예: HMAC SHA256, RSA)typ
: 토큰 타입 (JWT)
페이로드(Payload) 📦
{ "sub": "1234567890", "name": "홍길동", "role": "user", "iat": 1516239022, "exp": 1516242622 }
- 클레임(Claim): 사용자 ID, 이름, 권한 등의 정보
- 등록된 클레임(Registered claims):
iss
(발행자),exp
(만료시간),sub
(주제),aud
(대상) 등 - 공개 클레임(Public claims): 충돌 방지를 위한 이름을 가진 정보
- 비공개 클레임(Private claims): 당사자 간 정보 공유를 위한 사용자 정의 클레임
서명(Signature) ✅
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
- 헤더와 페이로드를 인코딩하고 비밀키로 서명
- 토큰이 변조되지 않았음을 검증
JWT 인증 프로세스
- 사용자가 ID/PW로 로그인 요청 → 서버가 검증 후 JWT 토큰 발급 🔑
- 클라이언트는 받은 토큰을 저장(localStorage, sessionStorage, 쿠키 등)
- 이후 API 요청 시 Authorization 헤더에 토큰을 포함하여 전송
Authorization: Bearer [JWT 토큰]
- 서버는 토큰의 서명을 검증하고 유효하면 요청 처리 ✓
- 토큰 만료 시 재로그인 또는 리프레시 토큰으로 갱신
사례 소개
JWT는 다양한 웹 서비스와 플랫폼에서 널리 사용되고 있습니다:
- REST API 인증: 구글, 페이스북과 같은 대형 서비스의 API 인증
- SPA(Single Page Application): React, Vue, Angular 등의 프레임워크 기반 앱
- 모바일 애플리케이션: 앱과 서버 간 인증
- 마이크로서비스: 서비스 간 인증 관리
Spring Security와 JWT 구현 예시
// JWT 토큰 생성 예시 (Java + Spring Security)
public String createToken(Authentication authentication) {
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
Date now = new Date();
Date expiryDate = new Date(now.getTime() + jwtExpirationInMs);
return Jwts.builder()
.setSubject(Long.toString(userPrincipal.getId()))
.claim("name", userPrincipal.getName())
.claim("role", userPrincipal.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()))
.setIssuedAt(new Date())
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
// 토큰 검증 예시
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
return true;
} catch (SignatureException ex) {
logger.error("유효하지 않은 JWT 서명");
} catch (MalformedJwtException ex) {
logger.error("유효하지 않은 JWT 토큰");
} catch (ExpiredJwtException ex) {
logger.error("만료된 JWT 토큰");
} catch (UnsupportedJwtException ex) {
logger.error("지원하지 않는 JWT 토큰");
} catch (IllegalArgumentException ex) {
logger.error("비어있는 JWT");
}
return false;
}
JWT 토큰 인증의 장단점 비교
구분 | 장점 | 단점 |
---|---|---|
성능 | 서버에 상태 저장 없음 (부하 감소) | 토큰 자체의 크기가 클 수 있음 |
확장성 | 다중 서버 환경에 적합 | - |
보안 | 서명으로 데이터 위변조 방지 | 한번 발급된 토큰 제어 어려움 |
호환성 | 다양한 플랫폼/언어에서 지원 | - |
구현 | 별도의 세션 스토리지 불필요 | 토큰 관리 로직 필요 |
주의사항 및 팁 💡
⚠️ 이것만은 주의하세요!
JWT 토큰 보안
- 토큰이 탈취되면 누구든 사용할 수 있음
- 민감한 정보는 페이로드에 포함하지 말 것(암호화X)
- 적절한 만료 시간 설정 필수
JWT 토큰 저장 위치
- localStorage: XSS 공격에 취약
- 쿠키: httpOnly, secure 플래그 설정 권장
Access Token + Refresh Token 전략
- Access Token: 짧은 유효기간(수분~수시간)
- Refresh Token: 긴 유효기간(수일~수주)
💡 꿀팁
- 토큰 크기를 최소화하려면 페이로드에 필요한 정보만 포함
- 중요 API는 토큰 검증 + 추가 권한 확인 로직 구현
- RTR(Refresh Token Rotation) 패턴: 리프레시 토큰 사용 시마다 새로운 리프레시 토큰 발급
- HTTPS 통신 필수: 토큰이 평문으로 전송되므로 암호화된 통신 채널 사용
마치며
지금까지 JWT 토큰 인증에 대해 알아보았습니다. 처음에는 어렵게 느껴질 수 있지만, 이 방식은 현대 웹 개발에서 필수적인 인증 방식입니다! 서버의 부하를 줄이고 확장성을 높이는 동시에, 적절한 보안 조치를 취한다면 안전하고 효율적인 인증 시스템을 구축할 수 있습니다. 여러분의 다음 프로젝트에 JWT를 도입해보는 건 어떨까요? 🚀
참고 자료 🔖
- JWT 인증 방식의 이해와 적용 - F-Lab
- JWT 토큰 인증 이란? (쿠키 vs 세션 vs 토큰) - Inpa Dev
- [JWT] 토큰(Token) 기반 인증에 대한 소개 - VELOPERT.LOG
- JWT를 이용한 로그인에서 보안을 높이는 방법 - velog
#JWT #인증 #토큰인증 #웹보안 #SpringSecurity
'800===Dev Docs and License > Web Security' 카테고리의 다른 글
OAuth 2.0 - 현대적 접근 권한 관리의 핵심 🔐 (1) | 2025.03.27 |
---|---|
대칭키 및 비대칭키 암호화 알고리즘 쉬운 설명 (0) | 2025.02.15 |
SHA Introduced (0) | 2024.06.04 |
RSA Introduced (0) | 2024.06.04 |
RSA Key Cryptosystem Introduced (0) | 2024.05.31 |