101 lines
3.4 KiB
Kotlin

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
import org.slf4j.MDC
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import roomescape.auth.business.CLAIM_TYPE_KEY
import roomescape.auth.exception.AuthErrorCode
import roomescape.auth.exception.AuthException
import roomescape.common.dto.MDC_PRINCIPAL_ID_KEY
import roomescape.common.dto.PrincipalType
import java.util.*
import javax.crypto.SecretKey
private val log: KLogger = KotlinLogging.logger {}
@Component
class JwtUtils(
@Value("\${security.jwt.token.secret-key}")
private val secretKeyString: String,
@Value("\${security.jwt.token.ttl-seconds}")
private val tokenTtlSeconds: Long
) {
private val secretKey: SecretKey = Keys.hmacShaKeyFor(secretKeyString.toByteArray())
fun createToken(subject: String, claims: Map<String, Any>): String {
log.debug { "[JwtUtils.createToken] 토큰 생성 시작: subject=$subject, claims=${claims}" }
val date = Date()
val accessTokenExpiredAt = Date(date.time + (tokenTtlSeconds * 1_000))
return Jwts.builder()
.subject(subject)
.claims(claims)
.issuedAt(date)
.expiration(accessTokenExpiredAt)
.signWith(secretKey)
.compact()
.also {
log.debug { "[JwtUtils.createToken] 토큰 생성 완료. token=${it}" }
}
}
fun extractIdAndType(token: String?): Pair<Long, PrincipalType> {
val id: Long = extractSubject(token)
.also { MDC.put(MDC_PRINCIPAL_ID_KEY, it) }
.toLong()
val type: PrincipalType = extractClaim(token, CLAIM_TYPE_KEY)
?.let { PrincipalType.valueOf(it) }
?: run {
log.info { "[JwtUtils.extractIdAndType] 회원 타입 조회 실패. id=$id" }
throw AuthException(AuthErrorCode.MEMBER_NOT_FOUND)
}
return id to type
}
fun extractSubject(token: String?): String {
if (token.isNullOrBlank()) {
throw AuthException(AuthErrorCode.TOKEN_NOT_FOUND)
}
val claims = extractAllClaims(token)
return claims.subject ?: run {
log.info { "[JwtUtils.extractSubject] subject를 찾을 수 없음.: token = ${token}" }
throw AuthException(AuthErrorCode.INVALID_TOKEN)
}
}
fun extractClaim(token: String?, key: String): String? {
if (token.isNullOrBlank()) {
throw AuthException(AuthErrorCode.TOKEN_NOT_FOUND)
}
val claims = extractAllClaims(token)
return claims.get(key, String::class.java)
}
private fun extractAllClaims(token: String): Claims {
try {
return Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token)
.payload
} catch (_: ExpiredJwtException) {
throw AuthException(AuthErrorCode.EXPIRED_TOKEN)
} catch (ex: Exception) {
log.warn { "[JwtUtils] 유효하지 않은 토큰 요청: ${ex.message}" }
throw AuthException(AuthErrorCode.INVALID_TOKEN)
}
}
}