generated from pricelees/issue-pr-template
refactor: JwtUtils에 Inerceptor / Resolver 공통 로직 생성 및 null claim 조회 시 로그 추가
This commit is contained in:
parent
26910f1d14
commit
2fc1cabe0e
@ -6,10 +6,14 @@ 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
|
||||||
|
import org.slf4j.MDC
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
|
import roomescape.auth.business.CLAIM_TYPE_KEY
|
||||||
import roomescape.auth.exception.AuthErrorCode
|
import roomescape.auth.exception.AuthErrorCode
|
||||||
import roomescape.auth.exception.AuthException
|
import roomescape.auth.exception.AuthException
|
||||||
|
import roomescape.common.dto.MDC_PRINCIPAL_ID_KEY
|
||||||
|
import roomescape.common.dto.PrincipalType
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.crypto.SecretKey
|
import javax.crypto.SecretKey
|
||||||
|
|
||||||
@ -43,25 +47,40 @@ class JwtUtils(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
fun extractSubject(token: String?): String {
|
||||||
if (token.isNullOrBlank()) {
|
if (token.isNullOrBlank()) {
|
||||||
throw AuthException(AuthErrorCode.TOKEN_NOT_FOUND)
|
throw AuthException(AuthErrorCode.TOKEN_NOT_FOUND)
|
||||||
}
|
}
|
||||||
val claims = extractAllClaims(token)
|
val claims = extractAllClaims(token)
|
||||||
|
|
||||||
return claims.subject ?: throw AuthException(AuthErrorCode.INVALID_TOKEN)
|
return claims.subject ?: run {
|
||||||
|
log.info { "[JwtUtils.extractSubject] subject를 찾을 수 없음.: token = ${token}" }
|
||||||
|
throw AuthException(AuthErrorCode.INVALID_TOKEN)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun extractClaim(token: String?, key: String): String {
|
fun extractClaim(token: String?, key: String): String? {
|
||||||
if (token.isNullOrBlank()) {
|
if (token.isNullOrBlank()) {
|
||||||
throw AuthException(AuthErrorCode.TOKEN_NOT_FOUND)
|
throw AuthException(AuthErrorCode.TOKEN_NOT_FOUND)
|
||||||
}
|
}
|
||||||
val claims = extractAllClaims(token)
|
val claims = extractAllClaims(token)
|
||||||
|
|
||||||
return claims.get(key, String::class.java) ?: run {
|
return claims.get(key, String::class.java)
|
||||||
log.warn { "[JwtUtils] Claim 조회 실패: key=$key" }
|
|
||||||
throw AuthException(AuthErrorCode.INVALID_TOKEN)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun extractAllClaims(token: String): Claims {
|
private fun extractAllClaims(token: String): Claims {
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import io.github.oshai.kotlinlogging.KLogger
|
|||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import jakarta.servlet.http.HttpServletRequest
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
import jakarta.servlet.http.HttpServletResponse
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
import org.slf4j.MDC
|
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
import org.springframework.web.method.HandlerMethod
|
import org.springframework.web.method.HandlerMethod
|
||||||
import org.springframework.web.servlet.HandlerInterceptor
|
import org.springframework.web.servlet.HandlerInterceptor
|
||||||
@ -14,8 +13,8 @@ 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.auth.web.support.AdminOnly
|
import roomescape.auth.web.support.AdminOnly
|
||||||
import roomescape.auth.web.support.MDC_MEMBER_ID_KEY
|
|
||||||
import roomescape.auth.web.support.accessToken
|
import roomescape.auth.web.support.accessToken
|
||||||
|
import roomescape.common.dto.PrincipalType
|
||||||
|
|
||||||
private val log: KLogger = KotlinLogging.logger {}
|
private val log: KLogger = KotlinLogging.logger {}
|
||||||
|
|
||||||
@ -35,20 +34,28 @@ class AdminInterceptor(
|
|||||||
val annotation: AdminOnly = handler.getMethodAnnotation(AdminOnly::class.java) ?: return true
|
val annotation: AdminOnly = handler.getMethodAnnotation(AdminOnly::class.java) ?: return true
|
||||||
|
|
||||||
val token: String? = request.accessToken()
|
val token: String? = request.accessToken()
|
||||||
val adminId = jwtUtils.extractSubject(token).also { MDC.put(MDC_MEMBER_ID_KEY, it) }
|
val (id, type) = jwtUtils.extractIdAndType(token)
|
||||||
|
|
||||||
jwtUtils.extractClaim(
|
val permission: AdminPermissionLevel = jwtUtils.extractClaim(token, key = CLAIM_PERMISSION_KEY)
|
||||||
token = token, key = CLAIM_PERMISSION_KEY
|
?.let {
|
||||||
).also {
|
AdminPermissionLevel.valueOf(it)
|
||||||
val permission = AdminPermissionLevel.valueOf(it)
|
}
|
||||||
|
?: run {
|
||||||
if (!permission.hasPrivilege(annotation.privilege)) {
|
if (type != PrincipalType.ADMIN) {
|
||||||
log.warn { "[AuthInterceptor] 관리자 권한 부족: required=${annotation.privilege} / current=${permission}" }
|
log.warn { "[AdminInterceptor] 회원의 관리자 API 접근: id=${id}" }
|
||||||
throw AuthException(AuthErrorCode.ACCESS_DENIED)
|
throw AuthException(AuthErrorCode.ACCESS_DENIED)
|
||||||
}
|
}
|
||||||
log.info { "[AuthInterceptor] 인증 완료. adminId=$adminId, permission=${permission}" }
|
log.warn { "[AdminInterceptor] 토큰에서 이용자 권한이 조회되지 않음: id=${id}" }
|
||||||
|
throw AuthException(AuthErrorCode.MEMBER_NOT_FOUND)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!permission.hasPrivilege(annotation.privilege)) {
|
||||||
|
log.warn { "[AdminInterceptor] 관리자 권한 부족: required=${annotation.privilege} / current=${permission}" }
|
||||||
|
throw AuthException(AuthErrorCode.ACCESS_DENIED)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info { "[AdminInterceptor] 인증 완료. adminId=$id, permission=${permission}" }
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,17 +4,13 @@ import io.github.oshai.kotlinlogging.KLogger
|
|||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import jakarta.servlet.http.HttpServletRequest
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
import jakarta.servlet.http.HttpServletResponse
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
import org.slf4j.MDC
|
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
import org.springframework.web.method.HandlerMethod
|
import org.springframework.web.method.HandlerMethod
|
||||||
import org.springframework.web.servlet.HandlerInterceptor
|
import org.springframework.web.servlet.HandlerInterceptor
|
||||||
import roomescape.auth.business.AuthServiceV2
|
import roomescape.auth.business.AuthServiceV2
|
||||||
import roomescape.auth.business.CLAIM_TYPE_KEY
|
|
||||||
import roomescape.auth.infrastructure.jwt.JwtUtils
|
import roomescape.auth.infrastructure.jwt.JwtUtils
|
||||||
import roomescape.auth.web.support.Authenticated
|
import roomescape.auth.web.support.Authenticated
|
||||||
import roomescape.auth.web.support.MDC_MEMBER_ID_KEY
|
|
||||||
import roomescape.auth.web.support.accessToken
|
import roomescape.auth.web.support.accessToken
|
||||||
import roomescape.common.dto.PrincipalType
|
|
||||||
|
|
||||||
private val log: KLogger = KotlinLogging.logger {}
|
private val log: KLogger = KotlinLogging.logger {}
|
||||||
|
|
||||||
@ -34,12 +30,10 @@ class AuthenticatedInterceptor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val token: String? = request.accessToken()
|
val token: String? = request.accessToken()
|
||||||
|
val (id, type) = jwtUtils.extractIdAndType(token)
|
||||||
val id = jwtUtils.extractSubject(token).also { MDC.put(MDC_MEMBER_ID_KEY, it) }
|
|
||||||
val type = jwtUtils.extractClaim(token, CLAIM_TYPE_KEY)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
authService.findContextById(id.toLong(), PrincipalType.valueOf(type))
|
authService.findContextById(id, type)
|
||||||
log.info { "[AuthenticatedInterceptor] 인증 완료. id=$id, type=${type}" }
|
log.info { "[AuthenticatedInterceptor] 인증 완료. id=$id, type=${type}" }
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|||||||
@ -4,15 +4,12 @@ import io.github.oshai.kotlinlogging.KLogger
|
|||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import jakarta.servlet.http.HttpServletRequest
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
import jakarta.servlet.http.HttpServletResponse
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
import org.slf4j.MDC
|
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
import org.springframework.web.method.HandlerMethod
|
import org.springframework.web.method.HandlerMethod
|
||||||
import org.springframework.web.servlet.HandlerInterceptor
|
import org.springframework.web.servlet.HandlerInterceptor
|
||||||
import roomescape.auth.business.CLAIM_TYPE_KEY
|
|
||||||
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.auth.web.support.MDC_MEMBER_ID_KEY
|
|
||||||
import roomescape.auth.web.support.UserOnly
|
import roomescape.auth.web.support.UserOnly
|
||||||
import roomescape.auth.web.support.accessToken
|
import roomescape.auth.web.support.accessToken
|
||||||
import roomescape.common.dto.PrincipalType
|
import roomescape.common.dto.PrincipalType
|
||||||
@ -34,17 +31,14 @@ class UserInterceptor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val token: String? = request.accessToken()
|
val token: String? = request.accessToken()
|
||||||
val userId = jwtUtils.extractSubject(token).also { id -> MDC.put(MDC_MEMBER_ID_KEY, id) }
|
val (id, type) = jwtUtils.extractIdAndType(token)
|
||||||
|
|
||||||
jwtUtils.extractClaim(token, CLAIM_TYPE_KEY).also {
|
if (type != PrincipalType.USER) {
|
||||||
if (it != PrincipalType.USER.name) {
|
log.warn { "[UserInterceptor] 관리자의 회원 API 접근: id=${id}" }
|
||||||
log.warn { "[UserInterceptor] 관리자의 회원 API 접근: id=${userId}" }
|
|
||||||
throw AuthException(AuthErrorCode.ACCESS_DENIED)
|
throw AuthException(AuthErrorCode.ACCESS_DENIED)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
log.info { "[AuthInterceptor] 인증 완료. userId=$userId" }
|
|
||||||
|
|
||||||
|
log.info { "[UserInterceptor] 인증 완료. userId=$id" }
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,13 +10,11 @@ import org.springframework.web.context.request.NativeWebRequest
|
|||||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver
|
import org.springframework.web.method.support.HandlerMethodArgumentResolver
|
||||||
import org.springframework.web.method.support.ModelAndViewContainer
|
import org.springframework.web.method.support.ModelAndViewContainer
|
||||||
import roomescape.auth.business.AuthServiceV2
|
import roomescape.auth.business.AuthServiceV2
|
||||||
import roomescape.auth.business.CLAIM_TYPE_KEY
|
|
||||||
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.auth.web.support.CurrentUser
|
import roomescape.auth.web.support.CurrentUser
|
||||||
import roomescape.auth.web.support.accessToken
|
import roomescape.auth.web.support.accessToken
|
||||||
import roomescape.common.dto.PrincipalType
|
|
||||||
|
|
||||||
private val log: KLogger = KotlinLogging.logger {}
|
private val log: KLogger = KotlinLogging.logger {}
|
||||||
|
|
||||||
@ -40,14 +38,12 @@ class CurrentUserContextResolver(
|
|||||||
val token: String? = request.accessToken()
|
val token: String? = request.accessToken()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val id: String = jwtUtils.extractSubject(token)
|
val (id, type) = jwtUtils.extractIdAndType(token)
|
||||||
val type: PrincipalType = PrincipalType.valueOf(jwtUtils.extractClaim(token, CLAIM_TYPE_KEY))
|
|
||||||
|
|
||||||
return authService.findContextById(id.toLong(), type)
|
return authService.findContextById(id, type)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
log.info { "[MemberIdResolver] 회원 조회 실패. message=${e.message}" }
|
log.info { "[MemberIdResolver] 회원 조회 실패. message=${e.message}" }
|
||||||
throw AuthException(AuthErrorCode.MEMBER_NOT_FOUND)
|
throw AuthException(AuthErrorCode.MEMBER_NOT_FOUND)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user