jwt
jwt 토큰 정보 읽어오기 & 예외처리
원코드
2024. 9. 4. 15:34
1. 토큰 분석(parse)
토큰을 생성할 때 사용한 secret Key를 전달하여 JwtParser를 생성하고, JwtParser의 parser() 함수를 이용하여 토큰을 분석한다.
private Jws<Claims> parseToken(final String token) {
return Jwts.parser()
//.setSigningKey(secretKey) -> deprecation
.verifyWith(secretKey)
.build()
//.parseClaimsJws(token)// -> deprecation
.parse(token)
.accept(Jws.CLAIMS);
}
verifyWith(Key key) : jwt 생성 시 사용한 시크릿 키
build() : JwtParser 리턴
parse(CharSequence jwt) : build()를 통해 생성한 JwtParser에 분석하고자 하는 jwt 토큰을 인자로 전달하여 결과로 jwt, jws, jwe 리턴
accept(JwtVisitor<Jws<Claims>> visitor) : parse(jwt)의 결과가 JWT, JWS 또는 JWE인지, 페이로드가 인스턴스 확인이 포함된 Claimsor byte[] 배열인지 확인하는 함수. if문 대신 유용하게 사용할 수 있음.
accept() 대신 if문을 사용할 경우(권장되지 않음)
// NOT RECOMMENDED:
Jwt<?,?> jwt = parser.parse(input);
if (jwt instanceof Jwe<?>) {
Jwe<?> jwe = (Jwe<?>)jwt;
if (jwe.getPayload() instanceof Claims) {
Jwe<Claims> claimsJwe = (Jwe<Claims>)jwe;
// do something with claimsJwe
}
}
2. 예외처리(커스텀)
parse(token) 호출 시 아래와 같은 exception이 발생할 수 있으므로 예외처리를 해주자.
Throws:
MalformedJwtException - if the specified JWT was incorrectly constructed (and therefore invalid).Invalid JWTs should not be trusted and should be discarded.
SignatureException - if a JWS signature was discovered, but could not be verified. JWTs that failsignature validation should not be trusted and should be discarded.
SecurityException - if the specified JWT string is a JWE and decryption fails
ExpiredJwtException - if the specified JWT is a Claims JWT and the Claims has an expiration timebefore the time this method is invoked.
IllegalArgumentException - if the specified string is null or empty or only whitespace.
예외처리 함수
public void validateTokens(final String token) {
try {
parseToken(token);
} catch (final ExpiredJwtException e) {
throw new ExpiredPeriodJwtException(ExceptionCode.EXPIRED_PERIOD_ACCESS_TOKEN); //커스텀 exception
} catch (final MalformedJwtException | IllegalArgumentException e) {
throw new InvalidJwtException(ExceptionCode.EXPIRED_PERIOD_ACCESS_TOKEN); //커스텀 exception
}
}
예외처리 클래스
ExpiredPeriodJwtException.class
import lombok.Getter;
@Getter
public class ExpiredPeriodJwtException extends AuthException{
public ExpiredPeriodJwtException(ExceptionCode exceptionCode) {
super(exceptionCode);
}
}
InvalidJwtException.class
import lombok.Getter;
@Getter
public class InvalidJwtException extends AuthException{
public InvalidJwtException(final ExceptionCode exceptionCode) {
super(exceptionCode);
}
}
AuthException.class
import lombok.Getter;
@Getter
public class AuthException extends RuntimeException{
private final int code;
private final String message;
public AuthException(final ExceptionCode code) {
this.code = code.getCode();
this.message = code.getMessage();
}
}
그리고 예외처리 결과를 json 형태로 클라이언트에 전달해 줄 핸들러 클래스
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(AuthException.class)
public ResponseEntity<ExceptionResponse> handleAuthException(AuthException e) {
log.warn(e.getMessage(), e);
return ResponseEntity.badRequest().body(new ExceptionResponse(e.getCode(), e.getMessage()));
}
}
3. 테스트
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.util.Random;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
import com.tlog.backend.global.exception.ExpiredPeriodJwtException;
import com.tlog.backend.global.exception.InvalidJwtException;
@SpringBootTest
@TestPropertySource(locations = "classpath:application.yml")
public class JwtProviderTest {
@Value("${jwt.secret}")
String secretKey;
@Value("${jwt.refresh_expiration_time}")
Long refreshTime;
@Value("${jwt.access_expiration_time}")
Long accessTime;
@Test
@DisplayName("토큰이 만료되었을 때 ExpiredPeriodJwtException 발생한다.")
void validateTokens_test1() {
//given
JwtProvider provider = new JwtProvider(secretKey, refreshTime, accessTime);
Long memberId = new Random().nextLong();
String token = provider.createToken(memberId, 0L);
//when, then
assertThrows(ExpiredPeriodJwtException.class, ()->{provider.validateTokens(token);});
}
@Test
@DisplayName("토큰 값이 잘못된 경우 InvalidJwtException 발생한다.")
void validateTokens_test2() {
//given
JwtProvider provider = new JwtProvider(secretKey, refreshTime, accessTime);
String token = null;
//when, then
assertThrows(InvalidJwtException.class, ()->{provider.validateTokens(token);});
}
}