Skip to content

Commit

Permalink
Merge pull request #23 from Katchup-dev/task#22
Browse files Browse the repository at this point in the history
[KS-12]  API 요청 토큰 검증 Argument Resolver 구현
  • Loading branch information
unanchoi authored May 7, 2024
2 parents 572d2d3 + 514ef1d commit ca19747
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 16 deletions.
5 changes: 5 additions & 0 deletions src/main/kotlin/site/katchup/springboot/auth/Auth.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package site.katchup.springboot.auth

@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
annotation class Auth()
15 changes: 15 additions & 0 deletions src/main/kotlin/site/katchup/springboot/auth/AuthConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package site.katchup.springboot.auth

import org.springframework.context.annotation.Configuration
import org.springframework.web.method.support.HandlerMethodArgumentResolver
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer

@Configuration
class AuthConfig(
private val authorizationArgumentResolver: AuthorizationArgumentResolver,
) : WebMvcConfigurer {

override fun addArgumentResolvers(resolvers: MutableList<HandlerMethodArgumentResolver>) {
resolvers.add(authorizationArgumentResolver)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package site.katchup.springboot.auth

import jakarta.servlet.http.HttpServletRequest
import org.springframework.core.MethodParameter
import org.springframework.stereotype.Component
import org.springframework.web.bind.support.WebDataBinderFactory
import org.springframework.web.context.request.NativeWebRequest
import org.springframework.web.method.support.HandlerMethodArgumentResolver
import org.springframework.web.method.support.ModelAndViewContainer
import site.katchup.springboot.exception.auth.InvalidTokenException
import site.katchup.springboot.global.message.FailMessage
import site.katchup.springboot.repository.MemberRepository

@Component
class AuthorizationArgumentResolver(
private val tokenValidator: TokenValidator,
private val memberRepository: MemberRepository,
) : HandlerMethodArgumentResolver {

companion object {
private const val AUTHORIZATION = "Authorization"
}
override fun supportsParameter(parameter: MethodParameter): Boolean {
return parameter.hasParameterAnnotation(Auth::class.java)
}

override fun resolveArgument(
parameter: MethodParameter,
mavContainer: ModelAndViewContainer?,
webRequest: NativeWebRequest,
binderFactory: WebDataBinderFactory?,
): Any? {
val request = webRequest.getNativeRequest(HttpServletRequest::class.java)
val jwt = request?.let { extractToken(it) } ?: throw InvalidTokenException(FailMessage.INVALID_TOKEN)
val memberId = tokenValidator.validate(jwt)
if (!memberRepository.existsById(memberId)) {
throw InvalidTokenException(FailMessage.INVALID_TOKEN)
}
return memberId
}

private fun extractToken(request: HttpServletRequest): String {
val token = request.getHeader(AUTHORIZATION)
if (token == null || !token.startsWith("Bearer ")) {
throw InvalidTokenException(FailMessage.INVALID_TOKEN)
}
return token.substring(7)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@ class TokenValidator(
@Value("\${katchup.jwt.secret-key}") private val secretKey: String,
) {

private val algorithm = Algorithm.HMAC256(secretKey)

companion object {
private const val MEMBER_ID = "memberId"
private const val TOKEN_ISSUER = "katchup"
}

fun validate(token: String): Long {
try {
val algorithm = Algorithm.HMAC256(secretKey)
val verifier: JWTVerifier = require(algorithm)
.withIssuer("katchup")
.withIssuer(TOKEN_ISSUER)
.withClaimPresence(MEMBER_ID)
.build()
val decodedJWT = verifier.verify(parse(token))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package site.katchup.springboot.controller
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PatchMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestBody
import site.katchup.springboot.auth.Auth
import site.katchup.springboot.dto.member.request.MemberAlarmRequest
import site.katchup.springboot.dto.member.request.MemberUpdateRequest
import site.katchup.springboot.dto.member.response.MemberAlarmResponse
Expand All @@ -22,22 +22,22 @@ class MemberController(
) {

@GetMapping("/members")
fun getMember(memberId: Long): ResponseEntity<BaseResponse<MemberResponse>> {
fun getMember(@Auth memberId: Long): ResponseEntity<BaseResponse<MemberResponse>> {
return BaseResponse.ok(SuccessMessage.SUCCESS_GET_MEMBER, memberService.getMemberById(memberId))
}

@PatchMapping("/members/{memberId}")
@PatchMapping("/members")
fun updateMember(
@PathVariable memberId: Long,
@Auth memberId: Long,
@RequestBody memberUpdateRequest: MemberUpdateRequest,
): ResponseEntity<BaseResponse<Unit>> {
memberProfileService.update(memberId, memberUpdateRequest)
return BaseResponse.ok(SuccessMessage.SUCCESS_UPDATE_MEMBER)
}

@PutMapping("/members/{memberId}/alarm")
@PutMapping("/members/alarm")
fun updateMemberAlarmConsent(
@PathVariable memberId: Long,
@Auth memberId: Long,
@RequestBody request: MemberAlarmRequest,
): ResponseEntity<BaseResponse<MemberAlarmResponse>> {
val response = memberProfileService.updateAlarmConsent(memberId, request)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import site.katchup.springboot.auth.Auth
import site.katchup.springboot.dto.notification.request.NotificationRequest
import site.katchup.springboot.dto.notification.response.NotificationListResponse
import site.katchup.springboot.dto.notification.response.NotificationResponse
Expand All @@ -17,12 +18,12 @@ class NotificationController(
) {

@GetMapping("/notifications")
fun getNotifications(memberId: Long): ResponseEntity<BaseResponse<NotificationListResponse>> {
fun getNotifications(@Auth memberId: Long): ResponseEntity<BaseResponse<NotificationListResponse>> {
return BaseResponse.ok(SuccessMessage.SUCCESS_GET_NOTIFICATIONS, notificationService.getNotifications(memberId))
}

@PostMapping("/notifications")
fun addNotification(memberId: Long, @RequestBody request: NotificationRequest): ResponseEntity<BaseResponse<NotificationResponse>> {
fun addNotification(@Auth memberId: Long, @RequestBody request: NotificationRequest): ResponseEntity<BaseResponse<NotificationResponse>> {
notificationService.addNotification(memberId, request)
return BaseResponse.ok(SuccessMessage.SUCCESS_ADD_NOTIFICATION)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.springframework.web.bind.annotation.PatchMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import site.katchup.springboot.auth.Auth
import site.katchup.springboot.dto.task.request.TaskRequest
import site.katchup.springboot.dto.task.request.TaskUpdateRequest
import site.katchup.springboot.dto.task.response.TaskResponse
Expand All @@ -20,7 +21,7 @@ class TaskController(

@PostMapping("/tasks")
fun addTask(
memberId: Long,
@Auth memberId: Long,
@RequestBody request: TaskRequest,
): ResponseEntity<BaseResponse<Unit>> {
taskService.addTask(memberId, request)
Expand All @@ -29,7 +30,7 @@ class TaskController(

@PatchMapping("/tasks/{taskId}")
fun updateTask(
memberId: Long,
@Auth memberId: Long,
@RequestBody request: TaskUpdateRequest,
@PathVariable taskId: Long,
): ResponseEntity<BaseResponse<Unit>> {
Expand All @@ -39,7 +40,7 @@ class TaskController(

@GetMapping("/tasks/{taskId}")
fun getTask(
memberId: Long,
@Auth memberId: Long,
@PathVariable taskId: Long,
): ResponseEntity<BaseResponse<TaskResponse>> {
val response = taskService.getTask(memberId, taskId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ class MemberControllerTest() : FunSpec() {
)
mockMvc.perform(
get("/api/v1/members")
.param("memberId", "1"),
.param("memberId", "1")
.header("Authorization", "Bearer eyasdasdas"),
)
.andExpect(status().isOk)
.andExpect(jsonPath("$.message").value("사용자 정보 조회 성공"))
Expand All @@ -52,7 +53,7 @@ class MemberControllerTest() : FunSpec() {
.andExpect(jsonPath("$.data.profileImage").value("https://katchup.site/image/1"))
}

test("[PATCH /api/v1/members/{memberId}] 사용자 프로필 업데이트 API Test") {
test("[PATCH /api/v1/members] 사용자 프로필 업데이트 API Test") {
every {
memberProfileService.update(any(), any())
} just runs
Expand All @@ -65,7 +66,8 @@ class MemberControllerTest() : FunSpec() {
val JSONBody = JsonUtil.getJSONStringBody(memberUpdateRequest)

mockMvc.perform(
patch("/api/v1/members/{memberId}", 1)
patch("/api/v1/members")
.param("memberId", "1")
.content(JSONBody)
.contentType("application/json"),
)
Expand Down

0 comments on commit ca19747

Please sign in to comment.