diff --git a/src/main/kotlin/com/hero/alignlab/config/dev/DevResourceCheckConfig.kt b/src/main/kotlin/com/hero/alignlab/config/dev/DevResourceCheckConfig.kt index 673b2ac..821a85a 100644 --- a/src/main/kotlin/com/hero/alignlab/config/dev/DevResourceCheckConfig.kt +++ b/src/main/kotlin/com/hero/alignlab/config/dev/DevResourceCheckConfig.kt @@ -1,11 +1,14 @@ package com.hero.alignlab.config.dev +import com.hero.alignlab.domain.auth.model.DevAuthToken +import com.hero.alignlab.domain.auth.model.DevAuthUserImpl import com.hero.alignlab.exception.ErrorCode import com.hero.alignlab.exception.NoAuthorityException import jakarta.validation.constraints.NotBlank import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Configuration +import reactor.core.publisher.Mono @Configuration @EnableConfigurationProperties(DevResourceCheckConfig.DevResourceCheckProperties::class) @@ -34,4 +37,14 @@ class DevResourceCheckConfig( throw NoAuthorityException(ErrorCode.NO_AUTHORITY_ERROR) } } + + fun checkDev(token: Mono): Mono { + return token.flatMap { + if (it.value != devResourceCheckProperties.key) { + Mono.error(NoAuthorityException(ErrorCode.NO_AUTHORITY_ERROR)) + } else { + Mono.just(DevAuthUserImpl()) + } + } + } } diff --git a/src/main/kotlin/com/hero/alignlab/config/swagger/SpringDocConfig.kt b/src/main/kotlin/com/hero/alignlab/config/swagger/SpringDocConfig.kt index d6ca141..4031e78 100644 --- a/src/main/kotlin/com/hero/alignlab/config/swagger/SpringDocConfig.kt +++ b/src/main/kotlin/com/hero/alignlab/config/swagger/SpringDocConfig.kt @@ -1,8 +1,9 @@ package com.hero.alignlab.config.swagger -import com.hero.alignlab.config.dev.DevResourceCheckConfig.Companion.DEV_AUTH_TOKEN_KEY import com.hero.alignlab.domain.auth.model.AUTH_TOKEN_KEY import com.hero.alignlab.domain.auth.model.AuthUser +import com.hero.alignlab.domain.auth.model.DEV_AUTH_TOKEN_KEY +import com.hero.alignlab.domain.auth.model.DevAuthUser import io.swagger.v3.oas.models.Components import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.info.Info @@ -33,16 +34,15 @@ class SpringDocConfig( .addRequestWrapperToIgnore( WebSession::class.java, RequestContext::class.java, - AuthUser::class.java + AuthUser::class.java, + DevAuthUser::class.java, ) } @Bean fun openApi(): OpenAPI { - val securityRequirement = SecurityRequirement().addList(AUTH_TOKEN_KEY) return OpenAPI() .components(authSetting()) - .security(listOf(securityRequirement)) .addServersItem(Server().url("/")) .info( Info() @@ -50,6 +50,8 @@ class SpringDocConfig( .version(buildProperties.version) .description("Hero Alignlab Rest API Docs") ) + .addSecurityItem(SecurityRequirement().addList(AUTH_TOKEN_KEY)) + .addSecurityItem(SecurityRequirement().addList(DEV_AUTH_TOKEN_KEY)) } private fun authSetting(): Components { @@ -57,10 +59,15 @@ class SpringDocConfig( .addSecuritySchemes( AUTH_TOKEN_KEY, SecurityScheme() - .description("Access Token") + .name(AUTH_TOKEN_KEY) + .type(SecurityScheme.Type.APIKEY) + .`in`(SecurityScheme.In.HEADER) + ).addSecuritySchemes( + DEV_AUTH_TOKEN_KEY, + SecurityScheme() + .name(DEV_AUTH_TOKEN_KEY) .type(SecurityScheme.Type.APIKEY) .`in`(SecurityScheme.In.HEADER) - .name(AUTH_TOKEN_KEY) ) } } diff --git a/src/main/kotlin/com/hero/alignlab/config/web/WebFluxConfig.kt b/src/main/kotlin/com/hero/alignlab/config/web/WebFluxConfig.kt index cb901ba..972d96d 100644 --- a/src/main/kotlin/com/hero/alignlab/config/web/WebFluxConfig.kt +++ b/src/main/kotlin/com/hero/alignlab/config/web/WebFluxConfig.kt @@ -1,7 +1,9 @@ package com.hero.alignlab.config.web import com.fasterxml.jackson.databind.ObjectMapper +import com.hero.alignlab.config.dev.DevResourceCheckConfig import com.hero.alignlab.domain.auth.application.AuthFacade +import com.hero.alignlab.domain.auth.resolver.ReactiveDevResolver import com.hero.alignlab.domain.auth.resolver.ReactiveUserResolver import org.springframework.context.annotation.Configuration import org.springframework.core.ReactiveAdapterRegistry @@ -23,6 +25,7 @@ import java.nio.charset.Charset class WebFluxConfig( private val objectMapper: ObjectMapper, private val authFacade: AuthFacade, + private val devResourceCheckConfig: DevResourceCheckConfig, ) : WebFluxConfigurer { override fun addCorsMappings(registry: CorsRegistry) { registry.addMapping("/**") @@ -51,6 +54,7 @@ class WebFluxConfig( configurer.addCustomResolver( ReactiveUserResolver(registry, authFacade), + ReactiveDevResolver(registry, devResourceCheckConfig), ReactiveSortHandlerMethodArgumentResolver(), ReactivePageableHandlerMethodArgumentResolver() ) diff --git a/src/main/kotlin/com/hero/alignlab/domain/auth/model/DevAuthUser.kt b/src/main/kotlin/com/hero/alignlab/domain/auth/model/DevAuthUser.kt new file mode 100644 index 0000000..2e9bcdd --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/auth/model/DevAuthUser.kt @@ -0,0 +1,37 @@ +package com.hero.alignlab.domain.auth.model + +import com.hero.alignlab.exception.ErrorCode +import com.hero.alignlab.exception.NotFoundException +import org.springframework.http.HttpHeaders + +const val DEV_AUTH_TOKEN_KEY = "X-HERO-DEV-TOKEN" + +interface DevAuthUser + +class DevAuthUserImpl : DevAuthUser + +data class DevAuthToken( + val key: String = DEV_AUTH_TOKEN_KEY, + val value: String, +) { + companion object { + fun HttpHeaders.resolveDevToken(): DevAuthToken { + return this.asSequence() + .filter { header -> isTokenHeader(header.key) } + .mapNotNull { header -> + header.value + .firstOrNull() + ?.takeIf { token -> token.isNotBlank() } + ?.let { token -> from(token) } + }.firstOrNull() ?: throw NotFoundException(ErrorCode.NOT_FOUND_TOKEN_ERROR) + } + + private fun isTokenHeader(headerKey: String): Boolean { + return DEV_AUTH_TOKEN_KEY.equals(headerKey, ignoreCase = true) + } + + private fun from(value: String): DevAuthToken { + return DevAuthToken(value = value) + } + } +} diff --git a/src/main/kotlin/com/hero/alignlab/domain/auth/resolver/ReactiveDevResolver.kt b/src/main/kotlin/com/hero/alignlab/domain/auth/resolver/ReactiveDevResolver.kt new file mode 100644 index 0000000..d1bf914 --- /dev/null +++ b/src/main/kotlin/com/hero/alignlab/domain/auth/resolver/ReactiveDevResolver.kt @@ -0,0 +1,39 @@ +package com.hero.alignlab.domain.auth.resolver + +import com.hero.alignlab.config.dev.DevResourceCheckConfig +import com.hero.alignlab.domain.auth.model.DevAuthToken +import com.hero.alignlab.domain.auth.model.DevAuthToken.Companion.resolveDevToken +import com.hero.alignlab.domain.auth.model.DevAuthUser +import org.springframework.core.MethodParameter +import org.springframework.core.ReactiveAdapterRegistry +import org.springframework.http.server.reactive.ServerHttpRequest +import org.springframework.web.reactive.BindingContext +import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolverSupport +import org.springframework.web.server.ServerWebExchange +import reactor.core.publisher.Mono +import reactor.kotlin.core.publisher.toMono + +class ReactiveDevResolver( + adapterRegistry: ReactiveAdapterRegistry, + private val devResourceCheckConfig: DevResourceCheckConfig, +) : HandlerMethodArgumentResolverSupport(adapterRegistry) { + override fun supportsParameter(parameter: MethodParameter): Boolean { + return parameter.parameterType == DevAuthUser::class.java + } + + override fun resolveArgument( + parameter: MethodParameter, + bindingContext: BindingContext, + exchange: ServerWebExchange, + ): Mono { + val tokenMono = resolveToken(exchange.request) + + return devResourceCheckConfig.checkDev(tokenMono) + } + + private fun resolveToken(request: ServerHttpRequest): Mono { + return request.headers + .resolveDevToken() + .toMono() + } +} diff --git a/src/main/kotlin/com/hero/alignlab/domain/dev/resource/DevDiscordWebhookResource.kt b/src/main/kotlin/com/hero/alignlab/domain/dev/resource/DevDiscordWebhookResource.kt index ac1bd29..807b448 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/dev/resource/DevDiscordWebhookResource.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/dev/resource/DevDiscordWebhookResource.kt @@ -3,12 +3,15 @@ package com.hero.alignlab.domain.dev.resource import com.hero.alignlab.batch.statistics.job.HeroStatisticsJob import com.hero.alignlab.client.discord.client.DiscordWebhookClient import com.hero.alignlab.client.discord.model.request.SendMessageRequest -import com.hero.alignlab.config.dev.DevResourceCheckConfig.Companion.devResource import com.hero.alignlab.config.swagger.SwaggerTag.DEV_TAG +import com.hero.alignlab.domain.auth.model.DevAuthUser import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.MediaType -import org.springframework.web.bind.annotation.* +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController import java.time.LocalDateTime @Tag(name = DEV_TAG) @@ -21,23 +24,19 @@ class DevDiscordWebhookResource( @Operation(summary = "discord webhook test") @PostMapping("/api/dev/v1/discord-webhooks/{id}") suspend fun sendMessage( - @RequestHeader("X-HERO-DEV-TOKEN") token: String, + dev: DevAuthUser, @RequestParam message: String, - ) = devResource(token) { - discordWebhookClient.sendMessage(SendMessageRequest(message)) - } + ) = discordWebhookClient.sendMessage(SendMessageRequest(message)) @Operation(summary = "discord webhook daily noti") @PostMapping("/api/dev/v1/discord-webhooks/daily-noti") suspend fun sendDailyNoti( - @RequestHeader("X-HERO-DEV-TOKEN") token: String, + dev: DevAuthUser, @RequestParam fromDateTime: LocalDateTime, @RequestParam toDateTime: LocalDateTime, - ) = devResource(token) { - statisticsJob.sendHeroStatistics( - title = "극락통계 테스트", - fromDate = fromDateTime, - toDate = toDateTime - ) - } + ) = statisticsJob.sendHeroStatistics( + title = "극락통계 테스트", + fromDate = fromDateTime, + toDate = toDateTime + ) } diff --git a/src/main/kotlin/com/hero/alignlab/domain/dev/resource/DevGroupResource.kt b/src/main/kotlin/com/hero/alignlab/domain/dev/resource/DevGroupResource.kt index f9aa3ed..52e5df1 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/dev/resource/DevGroupResource.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/dev/resource/DevGroupResource.kt @@ -1,7 +1,7 @@ package com.hero.alignlab.domain.dev.resource -import com.hero.alignlab.config.dev.DevResourceCheckConfig.Companion.devResource import com.hero.alignlab.config.swagger.SwaggerTag.DEV_TAG +import com.hero.alignlab.domain.auth.model.DevAuthUser import com.hero.alignlab.domain.dev.model.request.DevGroupJoinRequest import com.hero.alignlab.domain.group.application.GroupFacade import io.swagger.v3.oas.annotations.Operation @@ -18,14 +18,12 @@ class DevGroupResource( @Operation(summary = "그룹 조인하기") @PostMapping("/api/dev/v1/groups/{groupId}/join") suspend fun joinGroup( - @RequestHeader("X-HERO-DEV-TOKEN") token: String, + dev: DevAuthUser, @PathVariable groupId: Long, @RequestBody request: DevGroupJoinRequest, - ) = devResource(token) { - groupFacade.joinGroup( - groupId = groupId, - uid = request.uid, - joinCode = request.joinCode - ) - } + ) = groupFacade.joinGroup( + groupId = groupId, + uid = request.uid, + joinCode = request.joinCode + ) } diff --git a/src/main/kotlin/com/hero/alignlab/domain/dev/resource/DevPoseResource.kt b/src/main/kotlin/com/hero/alignlab/domain/dev/resource/DevPoseResource.kt index cba1ce6..ab4edfe 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/dev/resource/DevPoseResource.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/dev/resource/DevPoseResource.kt @@ -2,8 +2,8 @@ package com.hero.alignlab.domain.dev.resource import com.hero.alignlab.batch.posecount.job.PoseCountUpdateJob import com.hero.alignlab.common.extension.wrapVoid -import com.hero.alignlab.config.dev.DevResourceCheckConfig.Companion.devResource import com.hero.alignlab.config.swagger.SwaggerTag.DEV_TAG +import com.hero.alignlab.domain.auth.model.DevAuthUser import com.hero.alignlab.domain.dev.application.DevPoseService import com.hero.alignlab.domain.dev.model.request.DevPoseSnapshotRequest import io.swagger.v3.oas.annotations.Operation @@ -22,26 +22,20 @@ class DevPoseResource( @Operation(summary = "[DEV] 포즈 스냅샷 생성") @PostMapping("/api/dev/v1/pose-snapshots") suspend fun createPoseSnapshots( - @RequestHeader("X-HERO-DEV-TOKEN") token: String, + dev: DevAuthUser, @RequestBody request: DevPoseSnapshotRequest, - ) = devResource(token) { - devPoseService.create(request).wrapVoid() - } + ) = devPoseService.create(request).wrapVoid() @Operation(summary = "[DEV] 포즈 데이터 삭제") @DeleteMapping("/api/dev/v1/pose-snapshots/{id}") suspend fun deletePoseSnapshots( - @RequestHeader("X-HERO-DEV-TOKEN") token: String, - ) = devResource(token) { - devPoseService.deleteAllPoseData() - } + dev: DevAuthUser, + ) = devPoseService.deleteAllPoseData() @Operation(summary = "[DEV] 포즈 데이터 통계 처리") @PostMapping("/api/dev/v1/pose-counts") suspend fun updatePoseCounts( - @RequestHeader("X-HERO-DEV-TOKEN") token: String, + dev: DevAuthUser, @RequestParam targetDate: LocalDate - ) = devResource(token) { - poseCountUpdateJob.run(targetDate) - } + ) = poseCountUpdateJob.run(targetDate) } diff --git a/src/main/kotlin/com/hero/alignlab/domain/dev/resource/DevUserInfoResource.kt b/src/main/kotlin/com/hero/alignlab/domain/dev/resource/DevUserInfoResource.kt index dc2f8b8..f8a468c 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/dev/resource/DevUserInfoResource.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/dev/resource/DevUserInfoResource.kt @@ -1,13 +1,15 @@ package com.hero.alignlab.domain.dev.resource -import com.hero.alignlab.common.extension.wrapOk -import com.hero.alignlab.config.dev.DevResourceCheckConfig.Companion.devResource import com.hero.alignlab.config.swagger.SwaggerTag.DEV_TAG +import com.hero.alignlab.domain.auth.model.DevAuthUser import com.hero.alignlab.domain.user.application.UserInfoService import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.MediaType -import org.springframework.web.bind.annotation.* +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController @Tag(name = DEV_TAG) @RestController @@ -18,9 +20,7 @@ class DevUserInfoResource( @Operation(summary = "[DEV] 유저 정보 조회") @GetMapping("/api/dev/v1/users/{id}") suspend fun getUserInfo( + dev: DevAuthUser, @PathVariable id: Long, - @RequestHeader("X-HERO-DEV-TOKEN") token: String - ) = devResource(token) { - userInfoService.getUserInfo(id).wrapOk() - } + ) = userInfoService.getUserInfo(id) } diff --git a/src/main/kotlin/com/hero/alignlab/domain/dev/resource/DevWebsocketResource.kt b/src/main/kotlin/com/hero/alignlab/domain/dev/resource/DevWebsocketResource.kt index 33c15db..d996ae3 100644 --- a/src/main/kotlin/com/hero/alignlab/domain/dev/resource/DevWebsocketResource.kt +++ b/src/main/kotlin/com/hero/alignlab/domain/dev/resource/DevWebsocketResource.kt @@ -1,13 +1,12 @@ package com.hero.alignlab.domain.dev.resource -import com.hero.alignlab.config.dev.DevResourceCheckConfig.Companion.devResource import com.hero.alignlab.config.swagger.SwaggerTag.DEV_TAG +import com.hero.alignlab.domain.auth.model.DevAuthUser import com.hero.alignlab.domain.dev.application.DevWebsocketService import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.MediaType import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestHeader import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @@ -20,8 +19,6 @@ class DevWebsocketResource( @Operation(summary = "[DEV] websocket connection closed") @PostMapping("/api/dev/v1/websocket/connection-closed") suspend fun closedConnection( - @RequestHeader("X-HERO-DEV-TOKEN") token: String - ) = devResource(token) { - devWebsocketService.forceCloseAllWebSocketSessions() - } + dev: DevAuthUser, + ) = devWebsocketService.forceCloseAllWebSocketSessions() }