generated from pricelees/issue-pr-template
refactor: JwtUtils에서의 공통 부분 메서드 분리 & 만료 조건 추가 및 테스트
This commit is contained in:
parent
ea45673ef4
commit
e4f6ffe53d
@ -2,6 +2,7 @@ package roomescape.auth.infrastructure.jwt
|
|||||||
|
|
||||||
import io.github.oshai.kotlinlogging.KLogger
|
import io.github.oshai.kotlinlogging.KLogger
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import io.jsonwebtoken.Claims
|
||||||
import io.jsonwebtoken.ExpiredJwtException
|
import io.jsonwebtoken.ExpiredJwtException
|
||||||
import io.jsonwebtoken.Jwts
|
import io.jsonwebtoken.Jwts
|
||||||
import io.jsonwebtoken.security.Keys
|
import io.jsonwebtoken.security.Keys
|
||||||
@ -43,56 +44,37 @@ class JwtUtils(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun extractSubject(token: String?): String {
|
fun extractSubject(token: String?): String {
|
||||||
return runWithHandle {
|
if (token.isNullOrBlank()) {
|
||||||
log.debug { "[JwtUtils.extractSubject] subject 조회 시작: token=$token" }
|
throw AuthException(AuthErrorCode.TOKEN_NOT_FOUND)
|
||||||
|
}
|
||||||
|
val claims = extractAllClaims(token)
|
||||||
|
|
||||||
Jwts.parser()
|
return claims.subject ?: throw AuthException(AuthErrorCode.INVALID_TOKEN)
|
||||||
.verifyWith(secretKey)
|
|
||||||
.build()
|
|
||||||
.parseSignedClaims(token)
|
|
||||||
.payload
|
|
||||||
.subject
|
|
||||||
?.also {
|
|
||||||
log.debug { "[JwtUtils.extractSubject] subject 조회 완료: subject=${it}" }
|
|
||||||
}
|
|
||||||
?: run {
|
|
||||||
log.debug { "[JwtUtils.extractSubject] subject 조회 실패: token=${token}" }
|
|
||||||
throw AuthException(AuthErrorCode.MEMBER_NOT_FOUND)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun extractClaim(token: String?, key: String): String {
|
fun extractClaim(token: String?, key: String): String {
|
||||||
return runWithHandle {
|
if (token.isNullOrBlank()) {
|
||||||
log.debug { "[JwtUtils.extractClaim] claim 조회 시작: token=$token, claimKey=$key" }
|
throw AuthException(AuthErrorCode.TOKEN_NOT_FOUND)
|
||||||
|
}
|
||||||
|
val claims = extractAllClaims(token)
|
||||||
|
|
||||||
Jwts.parser()
|
return claims.get(key, String::class.java) ?: run {
|
||||||
|
log.warn { "[JwtUtils] Claim 조회 실패: key=$key" }
|
||||||
|
throw AuthException(AuthErrorCode.INVALID_TOKEN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractAllClaims(token: String): Claims {
|
||||||
|
try {
|
||||||
|
return Jwts.parser()
|
||||||
.verifyWith(secretKey)
|
.verifyWith(secretKey)
|
||||||
.build()
|
.build()
|
||||||
.parseSignedClaims(token)
|
.parseSignedClaims(token)
|
||||||
.payload
|
.payload
|
||||||
.get(key, String::class.java)
|
|
||||||
?.also {
|
|
||||||
log.debug { "[JwtHandler.extractClaim] claim 조회 완료: claim=${it}" }
|
|
||||||
}
|
|
||||||
?: run {
|
|
||||||
log.info { "[JwtUtils.extractClaim] claim=${key} 조회 실패: token=$token" }
|
|
||||||
throw AuthException(AuthErrorCode.MEMBER_NOT_FOUND)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun <T> runWithHandle(block: () -> T): T {
|
|
||||||
try {
|
|
||||||
return block()
|
|
||||||
} catch (e: AuthException) {
|
|
||||||
throw e
|
|
||||||
} catch (_: IllegalArgumentException) {
|
|
||||||
throw AuthException(AuthErrorCode.TOKEN_NOT_FOUND)
|
|
||||||
} catch (_: ExpiredJwtException) {
|
} catch (_: ExpiredJwtException) {
|
||||||
throw AuthException(AuthErrorCode.EXPIRED_TOKEN)
|
throw AuthException(AuthErrorCode.EXPIRED_TOKEN)
|
||||||
} catch (e: Exception) {
|
} catch (ex: Exception) {
|
||||||
log.warn { "[JwtUtils] 예외 발생: message=${e.message}" }
|
log.warn { "[JwtUtils] 유효하지 않은 토큰 요청: ${ex.message}" }
|
||||||
throw AuthException(AuthErrorCode.INVALID_TOKEN)
|
throw AuthException(AuthErrorCode.INVALID_TOKEN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,16 +3,17 @@ package roomescape.auth
|
|||||||
import io.kotest.assertions.throwables.shouldThrow
|
import io.kotest.assertions.throwables.shouldThrow
|
||||||
import io.kotest.core.spec.style.FunSpec
|
import io.kotest.core.spec.style.FunSpec
|
||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import org.junit.jupiter.api.assertThrows
|
|
||||||
import roomescape.auth.exception.AuthErrorCode
|
import roomescape.auth.exception.AuthErrorCode
|
||||||
import roomescape.auth.exception.AuthException
|
import roomescape.auth.exception.AuthException
|
||||||
import roomescape.auth.infrastructure.jwt.JwtUtils
|
import roomescape.auth.infrastructure.jwt.JwtUtils
|
||||||
import roomescape.common.config.next
|
import roomescape.common.config.next
|
||||||
import roomescape.util.tsidFactory
|
import roomescape.util.tsidFactory
|
||||||
|
|
||||||
class JwtUtilsTest(
|
class JwtUtilsTest : FunSpec() {
|
||||||
) : FunSpec() {
|
private val jwtUtils: JwtUtils = JwtUtils(
|
||||||
private val jwtUtils: JwtUtils = JwtUtils(secretKeyString = "caSf+JhhY9J9VcZxDQ7SNNOEIAJSZ9onsFstGNv9bjPHmHoTTcX+5wway5+//SPi", tokenTtlSeconds = 5)
|
secretKeyString = "caSf+JhhY9J9VcZxDQ7SNNOEIAJSZ9onsFstGNv9bjPHmHoTTcX+5wway5+//SPi",
|
||||||
|
tokenTtlSeconds = 5L
|
||||||
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
context("종합 테스트") {
|
context("종합 테스트") {
|
||||||
@ -35,7 +36,7 @@ class JwtUtilsTest(
|
|||||||
val claim = mapOf("name" to "sangdol")
|
val claim = mapOf("name" to "sangdol")
|
||||||
val commonToken = jwtUtils.createToken(subject, claim)
|
val commonToken = jwtUtils.createToken(subject, claim)
|
||||||
|
|
||||||
context("subject를 가져올 때 null 토큰을 입력하면 실패한다.") {
|
test("subject를 가져올 때 null 토큰을 입력하면 실패한다.") {
|
||||||
shouldThrow<AuthException> {
|
shouldThrow<AuthException> {
|
||||||
jwtUtils.extractSubject(null)
|
jwtUtils.extractSubject(null)
|
||||||
}.also {
|
}.also {
|
||||||
@ -43,7 +44,7 @@ class JwtUtilsTest(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context("claim을 가져올 때 null 토큰을 입력하면 실패한다.") {
|
test("claim을 가져올 때 null 토큰을 입력하면 실패한다.") {
|
||||||
shouldThrow<AuthException> {
|
shouldThrow<AuthException> {
|
||||||
jwtUtils.extractClaim(token = null, key = "")
|
jwtUtils.extractClaim(token = null, key = "")
|
||||||
}.also {
|
}.also {
|
||||||
@ -51,11 +52,40 @@ class JwtUtilsTest(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context("토큰은 유효하나 claim이 없으면 실패한다.") {
|
test("claim에 입력된 key의 정보가 없으면 실패한다.") {
|
||||||
shouldThrow<AuthException> {
|
shouldThrow<AuthException> {
|
||||||
jwtUtils.extractClaim(token = commonToken, key = "abcde")
|
jwtUtils.extractClaim(token = commonToken, key = "abcde")
|
||||||
}.also {
|
}.also {
|
||||||
it.errorCode shouldBe AuthErrorCode.MEMBER_NOT_FOUND
|
it.errorCode shouldBe AuthErrorCode.INVALID_TOKEN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("토큰이 만료되면 실패한다.") {
|
||||||
|
val jwtUtil = JwtUtils(
|
||||||
|
secretKeyString = "caSf+JhhY9J9VcZxDQ7SNNOEIAJSZ9onsFstGNv9bjPHmHoTTcX+5wway5+//SPi",
|
||||||
|
tokenTtlSeconds = 0L
|
||||||
|
)
|
||||||
|
|
||||||
|
val token = jwtUtil.createToken("hello", mapOf("name" to "sangdol"))
|
||||||
|
|
||||||
|
shouldThrow<AuthException> {
|
||||||
|
jwtUtil.extractSubject(token)
|
||||||
|
}.also {
|
||||||
|
it.errorCode shouldBe AuthErrorCode.EXPIRED_TOKEN
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldThrow<AuthException> {
|
||||||
|
jwtUtil.extractClaim(token, key = "name")
|
||||||
|
}.also {
|
||||||
|
it.errorCode shouldBe AuthErrorCode.EXPIRED_TOKEN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("토큰 형식이 잘못되면 실패한다.") {
|
||||||
|
shouldThrow<AuthException> {
|
||||||
|
jwtUtils.extractSubject("abcde")
|
||||||
|
}.also {
|
||||||
|
it.errorCode shouldBe AuthErrorCode.INVALID_TOKEN
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user