[#34] 회원 / 인증 도메인 재정의 #43

Merged
pricelees merged 73 commits from refactor/#34 into main 2025-09-13 10:13:45 +00:00
2 changed files with 60 additions and 48 deletions
Showing only changes of commit e4f6ffe53d - Show all commits

View File

@ -2,6 +2,7 @@ package roomescape.auth.infrastructure.jwt
import io.github.oshai.kotlinlogging.KLogger
import io.github.oshai.kotlinlogging.KotlinLogging
import io.jsonwebtoken.Claims
import io.jsonwebtoken.ExpiredJwtException
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.security.Keys
@ -43,56 +44,37 @@ class JwtUtils(
}
fun extractSubject(token: String?): String {
return runWithHandle {
log.debug { "[JwtUtils.extractSubject] subject 조회 시작: token=$token" }
Jwts.parser()
.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)
}
if (token.isNullOrBlank()) {
throw AuthException(AuthErrorCode.TOKEN_NOT_FOUND)
}
val claims = extractAllClaims(token)
return claims.subject ?: throw AuthException(AuthErrorCode.INVALID_TOKEN)
}
fun extractClaim(token: String?, key: String): String {
return runWithHandle {
log.debug { "[JwtUtils.extractClaim] claim 조회 시작: token=$token, claimKey=$key" }
if (token.isNullOrBlank()) {
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)
.build()
.parseSignedClaims(token)
.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) {
throw AuthException(AuthErrorCode.EXPIRED_TOKEN)
} catch (e: Exception) {
log.warn { "[JwtUtils] 예외 발생: message=${e.message}" }
} catch (ex: Exception) {
log.warn { "[JwtUtils] 유효하지 않은 토큰 요청: ${ex.message}" }
throw AuthException(AuthErrorCode.INVALID_TOKEN)
}
}

View File

@ -3,16 +3,17 @@ package roomescape.auth
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.assertThrows
import roomescape.auth.exception.AuthErrorCode
import roomescape.auth.exception.AuthException
import roomescape.auth.infrastructure.jwt.JwtUtils
import roomescape.common.config.next
import roomescape.util.tsidFactory
class JwtUtilsTest(
) : FunSpec() {
private val jwtUtils: JwtUtils = JwtUtils(secretKeyString = "caSf+JhhY9J9VcZxDQ7SNNOEIAJSZ9onsFstGNv9bjPHmHoTTcX+5wway5+//SPi", tokenTtlSeconds = 5)
class JwtUtilsTest : FunSpec() {
private val jwtUtils: JwtUtils = JwtUtils(
secretKeyString = "caSf+JhhY9J9VcZxDQ7SNNOEIAJSZ9onsFstGNv9bjPHmHoTTcX+5wway5+//SPi",
tokenTtlSeconds = 5L
)
init {
context("종합 테스트") {
@ -35,7 +36,7 @@ class JwtUtilsTest(
val claim = mapOf("name" to "sangdol")
val commonToken = jwtUtils.createToken(subject, claim)
context("subject를 가져올 때 null 토큰을 입력하면 실패한다.") {
test("subject를 가져올 때 null 토큰을 입력하면 실패한다.") {
shouldThrow<AuthException> {
jwtUtils.extractSubject(null)
}.also {
@ -43,7 +44,7 @@ class JwtUtilsTest(
}
}
context("claim을 가져올 때 null 토큰을 입력하면 실패한다.") {
test("claim을 가져올 때 null 토큰을 입력하면 실패한다.") {
shouldThrow<AuthException> {
jwtUtils.extractClaim(token = null, key = "")
}.also {
@ -51,11 +52,40 @@ class JwtUtilsTest(
}
}
context("토큰은 유효하나 claim이 없으면 실패한다.") {
test("claim에 입력된 key의 정보가 없으면 실패한다.") {
shouldThrow<AuthException> {
jwtUtils.extractClaim(token = commonToken, key = "abcde")
}.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
}
}
}