JwtUtil의 주요 역할
- JWT 생성
- 사용자의 ID, 이메일, 권한 등의 정보를 포함하는 JWT를 생성
- JWT는 클라이언트로 전달, 이후 요청에서 사용됨
- JWT 파싱 및 검증
- 클라이언트가 제공한 JWT를 파싱하여 토큰에 포함된 정보 추출
- 서명을 검증해 토큰이 변조되지 않았는지 검증
- 토큰의 유효 시간과 만료 시간을 확인
- JWT 관리
- HTTP 요청 헤더에 Bearer 접두사를 제거하여 순수한 JWT를 추출
- JWT를 기반으로 사용자 정보를 확인하여 인증 및 권한 처리
JwtUtil 코드
@Component
public class JwtUtil {
private static final String BEARER_PREFIX = "Bearer ";
private static final long TOKEN_TIME = 60 * 60 * 1000L; // 60분
private static final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
@Value("${jwt.secret.key}")
private String secretKey;
private Key key;
@PostConstruct
public void init() {
byte[] bytes = Base64.getDecoder().decode(secretKey);
key = Keys.hmacShaKeyFor(bytes);
}
public String createToken(Long userId, String email, UserRole userRole) {
Date date = new Date();
return BEARER_PREFIX +
Jwts.builder()
.setSubject(String.valueOf(userId))
.claim("email", email)
.claim("userRole", userRole)
.setExpiration(new Date(date.getTime() + TOKEN_TIME))
.setIssuedAt(date)
.signWith(key,signatureAlgorithm)
.compact();
}
public String substringToken(String tokenValue) throws ServerException {
if (StringUtils.hasText(tokenValue) && tokenValue.startsWith(BEARER_PREFIX)) {
return tokenValue.substring(7);
}
throw new ServerException("Not Found Token");
}
public Claims extractToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
}
}
코드 세부 분석
1. 상수 선언
private static final String BEARER_PREFIX = "Bearer ";
private static final long TOKEN_TIME = 60 * 60 * 1000L; // 60분
private static final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
- BEARER_PREFIX : JWT의 접두사로 사용되는 문자열 “Bearer “로, 헤더의 Authorization 필드에서 JWT를 구분
- TOKEN_TIME : JWT의 유효 시간을 60분(60 * 60 * 1000ms)으로 설정
- signatureAlgorithm : JWT 서명을 할 때 사용할 알고리즘을 HS256으로 설
2. 필드
@Value("${jwt.secret.key}")
private String secretKey;
private Key key;
- secretKey : JWT 서명 생성에 사용할 비밀 키로, application.properties와 같은 설정 파일이나 환경 변수로 주입
- key : JWT 서명 생성 및 검증에 사용할 HMAC 키 객체 저장
3. @PostConstruct 메서드 (init)
@PostConstruct
public void init() {
byte[] bytes = Base64.getDecoder().decode(secretKey);
key = Keys.hmacShaKeyFor(bytes);
}
- 클래스가 생성된 후 초기화 단계에서 호출
- secretKey를 Base64로 디코딩하여 HMAC 키를 생성. 이 키는 JWT를 생성하거나 검증할 때 사용
4. createToken 메서드
public String createToken(Long userId, String email, UserRole userRole) {
Date date = new Date(); // 현재 시간을 기준으로 Date 객체를 생성합니다. (토큰 생성 시간 및 만료 시간 계산에 사용)
return BEARER_PREFIX +
Jwts.builder() // JWT 생성 빌더 시작
.setSubject(String.valueOf(userId)) // subject 필드에 userId를 저장 (주로 사용자 식별자)
.claim("email", email) // 클레임에 "email" 키와 값을 저장
.claim("userRole", userRole) // 클레임에 "userRole" 키와 값을 저장
.setExpiration(new Date(date.getTime() + TOKEN_TIME)) // 현재 시간(date)에서 토큰 유효 시간(TOKEN_TIME)을 더하여 만료 시간 설정
.setIssuedAt(date) // 토큰 발급 시간 설정
.signWith(key,signatureAlgorithm) // 서명 생성: secretKey로 만든 키와 지정된 알고리즘(HS256)을 사용
.compact(); // JWT를 문자열 형태로 직렬화
}
- 순서
- JWT 생성
- 매개변수로 userId, email, UserRole을 받아 클레임에 저장 (매개변수는 프로젝트에 따라 변경될 수 있음)
- subject에 사용자 ID, email, userrole, expiration(토큰 만료 시간), issuedAt(토큰 생성 시간)이 JWT에 정보로 포함됨
- HMAC 알고리즘 키를 사용해 서명된 JWT를 반환하여 BEARER_PREFIX를 앞에 붙임
5. substringToken 메서드
public String substringToken(String tokenValue) throws ServerException {
if (StringUtils.hasText(tokenValue) && tokenValue.startsWith(BEARER_PREFIX)) { // 조건: tokenValue가 null 또는 공백이 아니고 "Bearer "로 시작해야 함
return tokenValue.substring(7); // "Bearer " 이후의 문자열(JWT 본문)만 반환
}
throw new ServerException("Not Found Token"); // 조건에 맞지 않는 경우 예외를 발생
}
- 순서
- 클라이언트에서 전달받은 JWT에서 Bearer 접두사를 제거
- 토큰이 유효하지 않으면 ServerException을 던짐
6. extractToken 메서드
public Claims extractToken(String token) {
return Jwts.parserBuilder() // JWT 파싱을 위한 빌더 생성
.setSigningKey(key) // 서명 검증을 위한 HMAC 키 설정
.build() // JWT 파서 빌드
.parseClaimsJws(token) // JWT 문자열을 파싱하고 클레임(Claims) 추출
.getBody(); // 파싱된 JWT의 본문(Claims) 반환
}
- 순서
- 전달받은 JWT를 파싱하여 클레임(Claims) 추출
- JWT의 서명을 검증하기 위해 key를 사용하며, 서명이 올바르지 않으면 예외 발생아이콘 추가