Skip to content

Commit ca19747

Browse files
authored
Merge pull request #23 from Katchup-dev/task#22
[KS-12] API 요청 토큰 검증 Argument Resolver 구현
2 parents 572d2d3 + 514ef1d commit ca19747

File tree

8 files changed

+91
-16
lines changed

8 files changed

+91
-16
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package site.katchup.springboot.auth
2+
3+
@Target(AnnotationTarget.VALUE_PARAMETER)
4+
@Retention(AnnotationRetention.RUNTIME)
5+
annotation class Auth()
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package site.katchup.springboot.auth
2+
3+
import org.springframework.context.annotation.Configuration
4+
import org.springframework.web.method.support.HandlerMethodArgumentResolver
5+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
6+
7+
@Configuration
8+
class AuthConfig(
9+
private val authorizationArgumentResolver: AuthorizationArgumentResolver,
10+
) : WebMvcConfigurer {
11+
12+
override fun addArgumentResolvers(resolvers: MutableList<HandlerMethodArgumentResolver>) {
13+
resolvers.add(authorizationArgumentResolver)
14+
}
15+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package site.katchup.springboot.auth
2+
3+
import jakarta.servlet.http.HttpServletRequest
4+
import org.springframework.core.MethodParameter
5+
import org.springframework.stereotype.Component
6+
import org.springframework.web.bind.support.WebDataBinderFactory
7+
import org.springframework.web.context.request.NativeWebRequest
8+
import org.springframework.web.method.support.HandlerMethodArgumentResolver
9+
import org.springframework.web.method.support.ModelAndViewContainer
10+
import site.katchup.springboot.exception.auth.InvalidTokenException
11+
import site.katchup.springboot.global.message.FailMessage
12+
import site.katchup.springboot.repository.MemberRepository
13+
14+
@Component
15+
class AuthorizationArgumentResolver(
16+
private val tokenValidator: TokenValidator,
17+
private val memberRepository: MemberRepository,
18+
) : HandlerMethodArgumentResolver {
19+
20+
companion object {
21+
private const val AUTHORIZATION = "Authorization"
22+
}
23+
override fun supportsParameter(parameter: MethodParameter): Boolean {
24+
return parameter.hasParameterAnnotation(Auth::class.java)
25+
}
26+
27+
override fun resolveArgument(
28+
parameter: MethodParameter,
29+
mavContainer: ModelAndViewContainer?,
30+
webRequest: NativeWebRequest,
31+
binderFactory: WebDataBinderFactory?,
32+
): Any? {
33+
val request = webRequest.getNativeRequest(HttpServletRequest::class.java)
34+
val jwt = request?.let { extractToken(it) } ?: throw InvalidTokenException(FailMessage.INVALID_TOKEN)
35+
val memberId = tokenValidator.validate(jwt)
36+
if (!memberRepository.existsById(memberId)) {
37+
throw InvalidTokenException(FailMessage.INVALID_TOKEN)
38+
}
39+
return memberId
40+
}
41+
42+
private fun extractToken(request: HttpServletRequest): String {
43+
val token = request.getHeader(AUTHORIZATION)
44+
if (token == null || !token.startsWith("Bearer ")) {
45+
throw InvalidTokenException(FailMessage.INVALID_TOKEN)
46+
}
47+
return token.substring(7)
48+
}
49+
}

src/main/kotlin/site/katchup/springboot/auth/TokenValidator.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,17 @@ class TokenValidator(
1414
@Value("\${katchup.jwt.secret-key}") private val secretKey: String,
1515
) {
1616

17+
private val algorithm = Algorithm.HMAC256(secretKey)
18+
1719
companion object {
1820
private const val MEMBER_ID = "memberId"
21+
private const val TOKEN_ISSUER = "katchup"
1922
}
2023

2124
fun validate(token: String): Long {
2225
try {
23-
val algorithm = Algorithm.HMAC256(secretKey)
2426
val verifier: JWTVerifier = require(algorithm)
25-
.withIssuer("katchup")
27+
.withIssuer(TOKEN_ISSUER)
2628
.withClaimPresence(MEMBER_ID)
2729
.build()
2830
val decodedJWT = verifier.verify(parse(token))

src/main/kotlin/site/katchup/springboot/controller/MemberController.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ package site.katchup.springboot.controller
33
import org.springframework.http.ResponseEntity
44
import org.springframework.web.bind.annotation.GetMapping
55
import org.springframework.web.bind.annotation.PatchMapping
6-
import org.springframework.web.bind.annotation.PathVariable
76
import org.springframework.web.bind.annotation.PutMapping
87
import org.springframework.web.bind.annotation.RequestBody
8+
import site.katchup.springboot.auth.Auth
99
import site.katchup.springboot.dto.member.request.MemberAlarmRequest
1010
import site.katchup.springboot.dto.member.request.MemberUpdateRequest
1111
import site.katchup.springboot.dto.member.response.MemberAlarmResponse
@@ -22,22 +22,22 @@ class MemberController(
2222
) {
2323

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

29-
@PatchMapping("/members/{memberId}")
29+
@PatchMapping("/members")
3030
fun updateMember(
31-
@PathVariable memberId: Long,
31+
@Auth memberId: Long,
3232
@RequestBody memberUpdateRequest: MemberUpdateRequest,
3333
): ResponseEntity<BaseResponse<Unit>> {
3434
memberProfileService.update(memberId, memberUpdateRequest)
3535
return BaseResponse.ok(SuccessMessage.SUCCESS_UPDATE_MEMBER)
3636
}
3737

38-
@PutMapping("/members/{memberId}/alarm")
38+
@PutMapping("/members/alarm")
3939
fun updateMemberAlarmConsent(
40-
@PathVariable memberId: Long,
40+
@Auth memberId: Long,
4141
@RequestBody request: MemberAlarmRequest,
4242
): ResponseEntity<BaseResponse<MemberAlarmResponse>> {
4343
val response = memberProfileService.updateAlarmConsent(memberId, request)

src/main/kotlin/site/katchup/springboot/controller/NotificationController.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import org.springframework.http.ResponseEntity
44
import org.springframework.web.bind.annotation.GetMapping
55
import org.springframework.web.bind.annotation.PostMapping
66
import org.springframework.web.bind.annotation.RequestBody
7+
import site.katchup.springboot.auth.Auth
78
import site.katchup.springboot.dto.notification.request.NotificationRequest
89
import site.katchup.springboot.dto.notification.response.NotificationListResponse
910
import site.katchup.springboot.dto.notification.response.NotificationResponse
@@ -17,12 +18,12 @@ class NotificationController(
1718
) {
1819

1920
@GetMapping("/notifications")
20-
fun getNotifications(memberId: Long): ResponseEntity<BaseResponse<NotificationListResponse>> {
21+
fun getNotifications(@Auth memberId: Long): ResponseEntity<BaseResponse<NotificationListResponse>> {
2122
return BaseResponse.ok(SuccessMessage.SUCCESS_GET_NOTIFICATIONS, notificationService.getNotifications(memberId))
2223
}
2324

2425
@PostMapping("/notifications")
25-
fun addNotification(memberId: Long, @RequestBody request: NotificationRequest): ResponseEntity<BaseResponse<NotificationResponse>> {
26+
fun addNotification(@Auth memberId: Long, @RequestBody request: NotificationRequest): ResponseEntity<BaseResponse<NotificationResponse>> {
2627
notificationService.addNotification(memberId, request)
2728
return BaseResponse.ok(SuccessMessage.SUCCESS_ADD_NOTIFICATION)
2829
}

src/main/kotlin/site/katchup/springboot/controller/TaskController.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import org.springframework.web.bind.annotation.PatchMapping
66
import org.springframework.web.bind.annotation.PathVariable
77
import org.springframework.web.bind.annotation.PostMapping
88
import org.springframework.web.bind.annotation.RequestBody
9+
import site.katchup.springboot.auth.Auth
910
import site.katchup.springboot.dto.task.request.TaskRequest
1011
import site.katchup.springboot.dto.task.request.TaskUpdateRequest
1112
import site.katchup.springboot.dto.task.response.TaskResponse
@@ -20,7 +21,7 @@ class TaskController(
2021

2122
@PostMapping("/tasks")
2223
fun addTask(
23-
memberId: Long,
24+
@Auth memberId: Long,
2425
@RequestBody request: TaskRequest,
2526
): ResponseEntity<BaseResponse<Unit>> {
2627
taskService.addTask(memberId, request)
@@ -29,7 +30,7 @@ class TaskController(
2930

3031
@PatchMapping("/tasks/{taskId}")
3132
fun updateTask(
32-
memberId: Long,
33+
@Auth memberId: Long,
3334
@RequestBody request: TaskUpdateRequest,
3435
@PathVariable taskId: Long,
3536
): ResponseEntity<BaseResponse<Unit>> {
@@ -39,7 +40,7 @@ class TaskController(
3940

4041
@GetMapping("/tasks/{taskId}")
4142
fun getTask(
42-
memberId: Long,
43+
@Auth memberId: Long,
4344
@PathVariable taskId: Long,
4445
): ResponseEntity<BaseResponse<TaskResponse>> {
4546
val response = taskService.getTask(memberId, taskId)

src/test/kotlin/site/katchup/springboot/controller/MemberControllerTest.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ class MemberControllerTest() : FunSpec() {
4141
)
4242
mockMvc.perform(
4343
get("/api/v1/members")
44-
.param("memberId", "1"),
44+
.param("memberId", "1")
45+
.header("Authorization", "Bearer eyasdasdas"),
4546
)
4647
.andExpect(status().isOk)
4748
.andExpect(jsonPath("$.message").value("사용자 정보 조회 성공"))
@@ -52,7 +53,7 @@ class MemberControllerTest() : FunSpec() {
5253
.andExpect(jsonPath("$.data.profileImage").value("https://katchup.site/image/1"))
5354
}
5455

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

6768
mockMvc.perform(
68-
patch("/api/v1/members/{memberId}", 1)
69+
patch("/api/v1/members")
70+
.param("memberId", "1")
6971
.content(JSONBody)
7072
.contentType("application/json"),
7173
)

0 commit comments

Comments
 (0)