Java | Spring
[Spring Boot] JWT (1) JSON Web Token
@leem
2025. 2. 25. 15:33
JWT(JSON Web Token)
JWT는 사용자 인증을 위한 토큰 기반 방식입니다.
기존의 세션 방식보다 가볍고 확장성이 뛰어나며, 서버 부하가 적습니다.
1. 기존 세션 방식 vs JWT 방식
웹에서 로그인 인증을 할 때, 기존의 세션 방식과 JWT 방식의 차이점을 먼저 살펴보겠습니다.
기존 세션 방식 (Session-based Authentication)
- 사용자가 로그인하면 서버가 세션을 생성하여 사용자 정보를 저장
- 이후 요청마다 세션 ID를 포함하여 서버에서 인증 확인
- 서버가 세션을 저장해야 하므로 확장성이 낮음 (부하 증가!)
JWT 방식 (Token-based Authentication)
- 사용자가 로그인하면 서버가 JWT(토큰)를 발급
- 이후 요청마다 JWT를 포함하여 인증
- 서버가 세션을 저장할 필요 없음 (확장성이 뛰어남!)
💡 JWT는 서버 부하를 줄이면서 인증을 간편하게 처리할 수 있는 방법
2. JWT의 구조
JWT는 Header(헤더), Payload(페이로드), Signature(서명) 3가지로 구성됩니다.
xxxxx.yyyyy.zzzzz // JWT 형식
Header (헤더)
JWT의 토큰 타입과 해싱 알고리즘 정보를 포함하는 부분입니다.
{
"alg": "HS256",
"typ": "JWT"
}
alg: 사용할 암호화 알고리즘 (예: HS256)
typ: 토큰의 타입 (JWT 사용)
Payload (페이로드)
사용자의 정보와 토큰 만료 시간(Exp) 을 포함하는 부분입니다.
{
"username": "john_doe",
"role": "admin",
"exp": 1700000000
}
username: 사용자의 아이디
role: 사용자의 역할 (예: admin, user)
exp: 토큰 만료 시간 (UNIX Timestamp 형식)
⚠️ 주의
- Payload는 Base64로 인코딩될 뿐, 암호화되지 않습니다!
- 비밀번호 같은 민감한 정보는 절대 포함하면 안 됩니다.
Signature (서명)
JWT가 위조되지 않았음을 보장하는 암호화된 해싱 값입니다.
- 서버가 비밀키(secret)를 사용해 생성
- 변조된 토큰인지 확인할 때 사용
3. JWT의 동작 방식
- 사용자가 로그인 요청
사용자가 username과 password를 입력하여 로그인 - 서버가 JWT를 생성하여 반환
서버는 로그인 성공 후 JWT를 생성하여 클라이언트에게 전달 - 클라이언트가 JWT를 저장
클라이언트(브라우저 또는 앱)는 받은 JWT를 로컬스토리지, 세션스토리지 또는 쿠키에 저장 - 이후 요청마다 JWT 포함
사용자는 API 요청을 보낼 때, Authorization 헤더에 JWT를 포함하여 보냄 - 서버가 JWT를 검증하여 사용자 확인
서버는 요청을 받을 때마다 JWT를 해독하여 사용자 정보를 확인
Authorization: Bearer xxxxx.yyyyy.zzzzz
- 토큰이 위조되었거나 만료되었으면 거부
- 토큰이 유효하면 요청을 정상 처리
- 액세스 토큰 → 짧은 만료 시간(10분)으로 설정하여 보안 강화
- 리프레시 토큰 → 긴 만료 시간(7일)로 설정하여 액세스 토큰 재발급
4. JWT를 활용한 로그인 예제 (Spring Boot)
1) User 모델 (User.java)
package com.tenco.class_jwt_v01.domain;
import lombok.Data;
@Data
public class User {
private Long id;
private String username;
private String password; // 보안을 위해 암호화 필요
private String refreshToken; // Refresh Token 저장용 (7일 동안 유지)
}
- username, password: 사용자의 로그인 정보
- refreshToken: JWT가 만료되었을 때 새 JWT를 발급받기 위한 토큰
2) UserMapper (UserMapper.java)
package com.tenco.class_jwt_v01.mapper;
import com.tenco.class_jwt_v01.domain.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper {
void save(User user); // 회원 저장
User findByUsername(String username); // 사용자 정보 조회
void updateRefreshToken(User user); // Refresh Token 업데이트
}
- 연결된 Mapper 인터페이스로, 사용자의 정보를 저장하거나 조회하는 역할
3) JWT 생성 및 검증 (JWTUtil.java)
JWT 생성 및 검증을 담당하는 유틸 클래스
- generateAccessToken() → 사용자 인증 후 액세스 토큰 발급 (10분 유효)
- generateRefreshToken() → 액세스 토큰 만료 시 리프레시 토큰 발급 (7일 유효)
- extractUsername() → JWT에서 username 값 추출
- extractRole() → JWT에서 role 값 추출
- validateToken() → 토큰이 유효한지 검사
package com.tenco.class_jwt_v01.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class JWTUtil {
@Value("${jwt.secret}")
private String secret; // JWT 암호화 키 (환경변수에서 관리)
// 📌 액세스 토큰 생성 (10분 유효)
public String generateAccessToken(String username) {
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
return JWT.create()
.withClaim("username", username)
.withClaim("role", "USER") // 역할 정보 포함
.withIssuedAt(new Date()) // 발급 시간
.withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 10)) // 10분 만료
.sign(algorithm);
} catch (JWTCreationException e) {
throw new RuntimeException("액세스 토큰 생성 실패", e);
}
}
// 📌 Refresh Token 생성 (7일 유효)
public String generateRefreshToken(String username) {
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
return JWT.create()
.withClaim("username", username)
.withClaim("role", "USER")
.withIssuedAt(new Date())
.withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 7)) // 7일 만료
.sign(algorithm);
} catch (JWTCreationException e) {
throw new RuntimeException("리프레시 토큰 생성 실패", e);
}
}
// 📌 JWT에서 username 추출
public String extractUsername(String token) {
return JWT.decode(token).getClaim("username").asString();
}
// 📌 토큰 유효성 검사
public boolean validateToken(String token, String username) {
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWT.require(algorithm).build().verify(token); // 검증
return extractUsername(token).equals(username); // 사용자가 일치하는지 확인
} catch (Exception e) {
return false;
}
}
}
생성
- 액세스 토큰과 리프레시 토큰을 각각 생성
- HMAC256 알고리즘으로 서명하여 위조 방지
- withExpiresAt()을 활용하여 만료 시간 설정
데이터 추출
- JWT의 Payload에서 username과 role 값을 추출하는 메서드
- Base64로 디코딩하여 쉽게 값을 확인 가능
검증 및 유효성 체크
- 토큰이 위조되지 않았는지 검증 (JWT.require().verify())
- 만료 시간을 확인하고, 토큰이 유효하면 true 반환