반응형
JWT 유틸리티 클래스 작성하기
1. JWT - Util 클래스 작성하기
- 이 코드는 JWT(JSON Web Token)를 생성하고 검증하는 데 사용되는 TokenUtils 클래스에 대한 것이다. 클래스는 여러 메서드를 포함하고 있으며, JWT의 생성, 유효성 검증 및 관련 정보 추출과 같은 다양한 기능을 수행한다.
@Slf4j
@Component
public class TokenUtils {
private static final String jwtSecretKey = "thisIsASecretKeyUsedForJwtTokenGenerationAndItIsLongEnoughToMeetTheRequirementOf256Bits";
// jwtSecretKey를 바이트 배열로 변환하고, 이를 사용하여 HMAC-SHA256 알고리즘에 사용할 키를 생성한다.
private static final Key key = Keys.hmacShaKeyFor(jwtSecretKey.getBytes(StandardCharsets.UTF_8));
private static final String JWT_TYPE = "JWT";
private static final String ALGORITHM = "HS256";
private static final String LOGIN_ID = "loginId";
private static final String USERNAME = "username";
/**
* 사용자 pk를 기준으로 JWT 토큰을 발급하여 반환해 준다.
*/
public static String generateJwtToken(UserDto userDto) {
JwtBuilder builder = Jwts.builder()
.setHeader(createHeader()) // Header 구성
.setClaims(createClaims(userDto)) // Payload - Claims구성
.setSubject(String.valueOf(userDto.loginId())) // Payload - Subjects구성
.setIssuer("profile") // Issuer 구성
.signWith(key, SignatureAlgorithm.HS256) // Signature 구성 : 이 키를 사용하여 JWT 토큰에 서명을 추가한다. 이 서명은 토큰의 무결성을 보장하는 데 사용된다.
.setExpiration(createExpiredDate()); // Expired Date 구성
return builder.compact();
}
/**
* 토큰을 기반으로 사용자의 정보를 반환해주는 메서드
*/
public static boolean isValidToken(String token) {
try {
Claims claims = getClaimsFormToken(token);
log.info("expireTime : " + claims.getExpiration());
log.info("loginId : " + claims.get(LOGIN_ID));
log.info("username : " + claims.get(USERNAME));
return true;
} catch (ExpiredJwtException expiredJwtException) {
log.error("Token Expired", expiredJwtException);
return false;
} catch (JwtException jwtException) {
log.error("Token Tampered", jwtException);
return false;
} catch (NullPointerException npe) {
log.error("Token is null", npe);
return false;
}
}
/**
* 토큰의 만료기간을 지정하는 함수
* @return Date
*/
private static Date createExpiredDate() {
// 토큰의 만료기간은 8시간으로 지정
Instant now = Instant.now();
Instant expiryDate = now.plus(Duration.ofHours(8));
return Date.from(expiryDate);
}
/**
* JWT의 헤더값을 생성해주는 메서드
*/
private static Map<String, Object> createHeader() {
Map<String, Object> header = new HashMap<>();
header.put("typ", JWT_TYPE);
header.put("alg", ALGORITHM);
header.put("regDate", System.currentTimeMillis());
return header;
}
/**
* 사용자 정보를 기반으로 클래임을 생성해주는 메서드
* @param userDto 사용자 정보
* @return Map<String, Object>
*/
private static Map<String, Object> createClaims(UserDto userDto) {
// 공개 클래임에 사용자의 이름과 이메일을 설정해서 정보를 조회할 수 있다.
Map<String, Object> claims = new HashMap<>();
log.info("loginId : " + userDto.loginId());
log.info("username : " + userDto.username());
claims.put(LOGIN_ID, userDto.loginId());
claims.put(USERNAME, userDto.username());
return claims;
}
/**
* 토큰 정보를 기반으로 Claims 정보를 반환받는 메서드
* @return Claims : Claims
*/
private static Claims getClaimsFormToken(String token) {
return Jwts.parserBuilder().setSigningKey(key)
.build().parseClaimsJws(token).getBody();
}
/**
* 토큰을 기반으로 사용자 정보를 반환받는 메서드
* @return String : 사용자 아이디
*/
public static String getUserIdFromToken(String token) {
Claims claims = getClaimsFormToken(token);
return claims.get(LOGIN_ID).toString();
}
}
1-2. JWT 토큰 생성
- generateJwtToken 메서드는 UserDto 객체를 매개변수로 받아 JWT 토큰을 생성한다. 토큰은 헤더, 클레임(claim), 서명으로 구성되며, 각 부분은 다음과 같은 목적을 가진다.
- 헤더(createHeader 메서드): JWT의 타입과 사용된 알고리즘을 정의한다. 여기서는 "JWT" 타입과 "HS256" 알고리즘이 사용된다.
- 클레임(createClaims 메서드): 사용자의 정보를 담는다. 이 경우, 사용자의 loginId와 username이 저장된다.
- 서명: 생성된 토큰의 무결성을 보장하기 위해 HMAC-SHA256 알고리즘을 사용한 서명이 추가된다.
- 만료시간(createExpiredDate 메서드): 토큰의 만료시간을 설정한다. 이 코드에서는 8시간 후로 설정되어 있다.
public static String generateJwtToken(UserDto userDto) {
// JWT 토큰을 생성하기 위한 빌더 객체를 초기화한다.
JwtBuilder builder = Jwts.builder()
.setHeader(createHeader()) // Header 구성
.setClaims(createClaims(userDto)) // Payload - Claims구성
.setSubject(String.valueOf(userDto.loginId())) // Payload - Subjects구성
.setIssuer("profile") // Issuer 구성
.signWith(key, SignatureAlgorithm.HS256) // Signature 구성 : 이 키를 사용하여 JWT 토큰에 서명을 추가한다. 이 서명은 토큰의 무결성을 보장하는 데 사용된다.
.setExpiration(createExpiredDate()); // Expired Date 구성
return builder.compact();
}
1-3. JWT 토큰 검증
- isValidToken 메서드는 주어진 JWT 토큰이 유효한지 검증한다. 토큰의 만료시간, 변조 여부, 널 여부를 체크하며, 각각의 경우에 대해 로그를 남기고, 유효하지 않은 경우 false를 반환한다.
public static boolean isValidToken(String token) {
try {
Claims claims = getClaimsFormToken(token);
log.info("expireTime : " + claims.getExpiration());
log.info("loginId : " + claims.get(LOGIN_ID));
log.info("username : " + claims.get(USERNAME));
return true;
} catch (ExpiredJwtException expiredJwtException) {
log.error("Token Expired", expiredJwtException);
return false;
} catch (JwtException jwtException) {
log.error("Token Tampered", jwtException);
return false;
} catch (NullPointerException npe) {
log.error("Token is null", npe);
return false;
}
}
1-4. createExpiredDate 메서드
- createExpiredDate 메서드는 JWT의 만료 시간을 설정한다. 이 메서드에서는 현재 시간으로부터 8시간 후를 만료 시간으로 설정하고 있다. 만료 시간은 토큰의 유효 기간을 정의하는 중요한 부분으로, 이 시간이 지나면 토큰이 더 이상 유효하지 않게 된다. 만료 시간을 설정함으로써, 오래된 토큰을 통한 잠재적인 보안 위험을 줄일 수 있다.
private static Date createExpiredDate() {
// 토큰의 만료기간은 8시간으로 지정
Instant now = Instant.now();
Instant expiryDate = now.plus(Duration.ofHours(8));
return Date.from(expiryDate);
}
1-5. createHeader 메서드
- createHeader 메서드는 JWT의 헤더를 생성한다. JWT 헤더는 토큰의 타입과 사용된 알고리즘에 대한 정보를 담는 부분이다. 이 메서드에서는 헤더에 'JWT' 타입(typ)과 'HS256' 알고리즘(alg)을 설정하고 있다. 또한, 현재 시간을 밀리초 단위로 표시하는 'regDate' 필드를 포함시켜 토큰이 생성된 시간을 기록한다. 이 정보는 토큰을 검증하는 데 필요한 기본적인 메타데이터를 제공한다.
private static Map<String, Object> createHeader() {
Map<String, Object> header = new HashMap<>();
header.put("typ", JWT_TYPE);
header.put("alg", ALGORITHM);
header.put("regDate", System.currentTimeMillis());
return header;
}
1-6. createClaims 메서드
- createClaims 메서드는 JWT의 클레임(claim)을 구성한다. 클레임은 토큰의 내용을 담는 부분으로, 사용자의 정보나 권한 등의 데이터를 포함할 수 있다. 이 메서드에서는 UserDto 객체를 받아 사용자의 loginId와 username을 클레임에 포함시키고 있다. 이렇게 설정된 클레임은 토큰을 사용하는 사용자의 식별 정보를 제공하며, 서버에서 사용자를 인식하고 권한을 체크하는 데 사용된다.
private static Map<String, Object> createClaims(UserDto userDto) {
// 공개 클래임에 사용자의 이름과 이메일을 설정해서 정보를 조회할 수 있다.
Map<String, Object> claims = new HashMap<>();
log.info("loginId : " + userDto.loginId());
log.info("username : " + userDto.username());
claims.put(LOGIN_ID, userDto.loginId());
claims.put(USERNAME, userDto.username());
return claims;
}
1-7. 토큰에서 사용자 정보 추출
- getUserIdFromToken 메서드는 토큰에 저장된 사용자의 loginId를 반환한다. 이 메서드는 getClaimsFormToken 메서드를 사용하여 토큰의 클레임을 추출하고, 이를 기반으로 사용자 ID를 얻는다.
private static Claims getClaimsFormToken(String token) {
return Jwts.parserBuilder().setSigningKey(key)
.build().parseClaimsJws(token).getBody();
}
public static String getUserIdFromToken(String token) {
Claims claims = getClaimsFormToken(token);
return claims.get(LOGIN_ID).toString();
}
다음 포스트에서는 시큐리티에서 사용할 UserDetailService와 Dto객체를 생성해보자👇🏻👇🏻
반응형