diff --git a/src/main/kotlin/roomescape/auth/web/support/AuthAnnotations.kt b/src/main/kotlin/roomescape/auth/web/support/AuthAnnotations.kt index 3f98cdf1..2fb7af52 100644 --- a/src/main/kotlin/roomescape/auth/web/support/AuthAnnotations.kt +++ b/src/main/kotlin/roomescape/auth/web/support/AuthAnnotations.kt @@ -1,5 +1,7 @@ package roomescape.auth.web.support +import roomescape.admin.infrastructure.persistence.Privilege + @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) annotation class Admin @@ -10,4 +12,10 @@ annotation class LoginRequired @Target(AnnotationTarget.VALUE_PARAMETER) @Retention(AnnotationRetention.RUNTIME) -annotation class MemberId \ No newline at end of file +annotation class MemberId + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class AdminOnly( + val privilege: Privilege +) diff --git a/src/main/kotlin/roomescape/auth/web/support/interceptors/AdminInterceptor.kt b/src/main/kotlin/roomescape/auth/web/support/interceptors/AdminInterceptor.kt new file mode 100644 index 00000000..6361fdca --- /dev/null +++ b/src/main/kotlin/roomescape/auth/web/support/interceptors/AdminInterceptor.kt @@ -0,0 +1,54 @@ +package roomescape.auth.web.support.interceptors + +import io.github.oshai.kotlinlogging.KLogger +import io.github.oshai.kotlinlogging.KotlinLogging +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.slf4j.MDC +import org.springframework.stereotype.Component +import org.springframework.web.method.HandlerMethod +import org.springframework.web.servlet.HandlerInterceptor +import roomescape.admin.infrastructure.persistence.AdminPermissionLevel +import roomescape.auth.business.CLAIM_PERMISSION_KEY +import roomescape.auth.exception.AuthErrorCode +import roomescape.auth.exception.AuthException +import roomescape.auth.infrastructure.jwt.JwtUtils +import roomescape.auth.web.support.AdminOnly +import roomescape.auth.web.support.MDC_MEMBER_ID_KEY +import roomescape.auth.web.support.accessToken + +private val log: KLogger = KotlinLogging.logger {} + +@Component +class AdminInterceptor( + private val jwtUtils: JwtUtils +) : HandlerInterceptor { + override fun preHandle( + request: HttpServletRequest, + response: HttpServletResponse, + handler: Any + ): Boolean { + if (handler !is HandlerMethod) { + return true + } + + val annotation: AdminOnly = handler.getMethodAnnotation(AdminOnly::class.java) ?: return true + + val token: String? = request.accessToken() + val adminId = jwtUtils.extractSubject(token).also { MDC.put(MDC_MEMBER_ID_KEY, it) } + + jwtUtils.extractClaim( + token = token, key = CLAIM_PERMISSION_KEY + ).also { + val permission = AdminPermissionLevel.valueOf(it) + + if (!permission.hasPrivilege(annotation.privilege)) { + log.warn { "[AuthInterceptor] 관리자 권한 부족: required=${annotation.privilege} / current=${permission}" } + throw AuthException(AuthErrorCode.ACCESS_DENIED) + } + log.info { "[AuthInterceptor] 인증 완료. adminId=$adminId, permission=${permission}" } + } + + return true + } +}