Skip to content

Commit

Permalink
[Refactor/#392] HandlerMethodArgumentResolver 적용 (#394)
Browse files Browse the repository at this point in the history
* refactor: toAuthorities 메서드 분리

* refactor: 상속을 위한 TokenUserDetails open 클래스로 변경

* feat: UserArgument 구현

* refactor: UserArgument 적용

* refactor: AuthorityUtils 적용

* refactor: UserArgumentHandlerMethodArgumentResolver 반환 구체화

* feat: UserArgumentHandlerMethodArgumentResolver 설정에 등록
  • Loading branch information
belljun3395 authored Sep 11, 2024
1 parent 2188971 commit 6aa3191
Show file tree
Hide file tree
Showing 10 changed files with 124 additions and 55 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.few.api.security.authentication.authority

import org.apache.commons.lang3.StringUtils
import org.springframework.security.core.GrantedAuthority

object AuthorityUtils {

@Throws(IllegalArgumentException::class)
fun toAuthorities(roles: String): List<GrantedAuthority> {
val tokens = StringUtils.splitPreserveAllTokens(roles, "[,]")
val rtn: MutableList<GrantedAuthority> = ArrayList()
for (token in tokens) {
if (token != "") {
val role = token.trim { it <= ' ' }
rtn.add(Roles.valueOf(role).authority)
}
}
return rtn
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package com.few.api.security.authentication.token
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.userdetails.UserDetails

class TokenUserDetails(
open class TokenUserDetails(
val authorities: List<GrantedAuthority>,
val id: String,
val email: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package com.few.api.security.authentication.token

import com.few.api.security.authentication.authority.Roles
import com.few.api.security.authentication.authority.AuthorityUtils
import com.few.api.security.exception.AccessTokenInvalidException
import com.few.api.security.token.TokenResolver
import io.github.oshai.kotlinlogging.KotlinLogging
import io.jsonwebtoken.Claims
import org.apache.commons.lang3.StringUtils
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.stereotype.Component
Expand Down Expand Up @@ -44,24 +42,8 @@ class TokenUserDetailsService(
String::class.java
)

val authorities = toAuthorities(roles)
val authorities = AuthorityUtils.toAuthorities(roles)

return TokenUserDetails(authorities, id.toString(), email)
}

private fun toAuthorities(roles: String): List<GrantedAuthority> {
val tokens = StringUtils.splitPreserveAllTokens(roles, "[,]")
val rtn: MutableList<GrantedAuthority> = ArrayList()
for (token in tokens) {
if (token != "") {
val role = token.trim { it <= ' ' }
try {
rtn.add(Roles.valueOf(role).authority)
} catch (exception: IllegalArgumentException) {
log.error { "${"Invalid role. role: {}"} $role" }
}
}
}
return rtn
}
}
10 changes: 9 additions & 1 deletion api/src/main/kotlin/com/few/api/web/config/WebConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@ package com.few.api.web.config
import com.few.api.web.config.converter.DayCodeConverter
import com.few.api.web.config.converter.ViewConverter
import com.few.api.web.config.converter.WorkBookCategoryConverter
import com.few.api.web.support.method.UserArgumentHandlerMethodArgumentResolver
import org.springframework.context.annotation.Configuration
import org.springframework.format.FormatterRegistry
import org.springframework.web.cors.CorsConfiguration
import org.springframework.web.method.support.HandlerMethodArgumentResolver
import org.springframework.web.servlet.config.annotation.CorsRegistry
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer

@Configuration
class WebConfig : WebMvcConfigurer {
class WebConfig(
private val userArgumentHandlerMethodArgumentResolver: UserArgumentHandlerMethodArgumentResolver,
) : WebMvcConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/**")
.allowedOriginPatterns(CorsConfiguration.ALL)
Expand All @@ -31,4 +35,8 @@ class WebConfig : WebMvcConfigurer {
registry.addConverter(ViewConverter())
registry.addConverter(DayCodeConverter())
}

override fun addArgumentResolvers(argumentResolvers: MutableList<HandlerMethodArgumentResolver>) {
argumentResolvers.add(userArgumentHandlerMethodArgumentResolver)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ import com.few.api.domain.article.usecase.ReadArticleUseCase
import com.few.api.domain.article.usecase.BrowseArticlesUseCase
import com.few.api.domain.article.usecase.dto.ReadArticleUseCaseIn
import com.few.api.domain.article.usecase.dto.ReadArticlesUseCaseIn
import com.few.api.security.filter.token.AccessTokenResolver
import com.few.api.security.token.TokenResolver
import com.few.api.web.controller.article.response.ReadArticleResponse
import com.few.api.web.controller.article.response.ReadArticlesResponse
import com.few.api.web.controller.article.response.WorkbookInfo
import com.few.api.web.controller.article.response.WriterInfo
import com.few.api.web.support.ApiResponse
import com.few.api.web.support.ApiResponseGenerator
import com.few.api.web.support.method.UserArgument
import com.few.api.web.support.method.UserArgumentDetails
import com.few.data.common.code.CategoryType
import jakarta.servlet.http.HttpServletRequest
import jakarta.validation.constraints.Min
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
Expand All @@ -26,22 +25,16 @@ import org.springframework.web.bind.annotation.*
class ArticleController(
private val readArticleUseCase: ReadArticleUseCase,
private val browseArticlesUseCase: BrowseArticlesUseCase,
private val tokenResolver: TokenResolver,
) {

@GetMapping("/{articleId}")
fun readArticle(
servletRequest: HttpServletRequest,
@UserArgument userArgumentDetails: UserArgumentDetails,
@PathVariable(value = "articleId")
@Min(value = 1, message = "{min.id}")
articleId: Long,
): ApiResponse<ApiResponse.SuccessBody<ReadArticleResponse>> {
val authorization: String? = servletRequest.getHeader("Authorization")
val memberId = authorization?.let {
AccessTokenResolver.resolve(it)
}.let {
tokenResolver.resolveId(it)
} ?: 0L
val memberId = userArgumentDetails.id.toLong()

val useCaseOut = ReadArticleUseCaseIn(
articleId = articleId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ import com.few.api.domain.workbook.usecase.BrowseWorkbooksUseCase
import com.few.api.domain.workbook.usecase.dto.ReadWorkbookUseCaseIn
import com.few.api.domain.workbook.usecase.ReadWorkbookUseCase
import com.few.api.domain.workbook.usecase.dto.BrowseWorkbooksUseCaseIn
import com.few.api.security.filter.token.AccessTokenResolver
import com.few.api.security.token.TokenResolver
import com.few.api.web.controller.workbook.response.*
import com.few.api.web.support.WorkBookCategory
import com.few.api.web.support.ApiResponse
import com.few.api.web.support.ApiResponseGenerator
import com.few.api.web.support.ViewCategory
import jakarta.servlet.http.HttpServletRequest
import com.few.api.web.support.method.UserArgument
import com.few.api.web.support.method.UserArgumentDetails
import jakarta.validation.constraints.Min
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
Expand All @@ -28,7 +27,6 @@ import org.springframework.web.bind.annotation.RestController
class WorkBookController(
private val readWorkbookUseCase: ReadWorkbookUseCase,
private val browseWorkBooksUseCase: BrowseWorkbooksUseCase,
private val tokenResolver: TokenResolver,
) {

@GetMapping("/categories")
Expand All @@ -48,18 +46,14 @@ class WorkBookController(

@GetMapping
fun browseWorkBooks(
servletRequest: HttpServletRequest,
@UserArgument userArgumentDetails: UserArgumentDetails,
@RequestParam(value = "category", required = false)
category: WorkBookCategory?,
@RequestParam(value = "view", required = false)
viewCategory: ViewCategory?,
): ApiResponse<ApiResponse.SuccessBody<BrowseWorkBooksResponse>> {
val authorization: String? = servletRequest.getHeader("Authorization")
val memberId = authorization?.let {
AccessTokenResolver.resolve(it)
}.let {
tokenResolver.resolveId(it)
}
val memberId = userArgumentDetails.id.toLong()

val useCaseOut =
BrowseWorkbooksUseCaseIn(category ?: WorkBookCategory.All, viewCategory, memberId).let { useCaseIn ->
browseWorkBooksUseCase.execute(useCaseIn)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ package com.few.api.web.controller.workbook.article

import com.few.api.domain.workbook.article.dto.ReadWorkBookArticleUseCaseIn
import com.few.api.domain.workbook.article.usecase.ReadWorkBookArticleUseCase
import com.few.api.security.filter.token.AccessTokenResolver
import com.few.api.security.token.TokenResolver
import com.few.api.web.controller.workbook.article.response.ReadWorkBookArticleResponse
import com.few.api.web.support.ApiResponse
import com.few.api.web.support.ApiResponseGenerator
import jakarta.servlet.http.HttpServletRequest
import com.few.api.web.support.method.UserArgument
import com.few.api.web.support.method.UserArgumentDetails
import jakarta.validation.constraints.Min
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
Expand All @@ -22,25 +21,19 @@ import org.springframework.web.bind.annotation.RestController
@RequestMapping(value = ["/api/v1/workbooks/{workbookId}/articles"], produces = [MediaType.APPLICATION_JSON_VALUE])
class WorkBookArticleController(
private val readWorkBookArticleUseCase: ReadWorkBookArticleUseCase,
private val tokenResolver: TokenResolver,
) {

@GetMapping("/{articleId}")
fun readWorkBookArticle(
servletRequest: HttpServletRequest,
@UserArgument userArgumentDetails: UserArgumentDetails,
@PathVariable(value = "workbookId")
@Min(value = 1, message = "{min.id}")
workbookId: Long,
@PathVariable(value = "articleId")
@Min(value = 1, message = "{min.id}")
articleId: Long,
): ApiResponse<ApiResponse.SuccessBody<ReadWorkBookArticleResponse>> {
val authorization: String? = servletRequest.getHeader("Authorization")
val memberId = authorization?.let {
AccessTokenResolver.resolve(it)
}.let {
tokenResolver.resolveId(it)
} ?: 0L
val memberId = userArgumentDetails.id.toLong()

val useCaseOut = ReadWorkBookArticleUseCaseIn(
workbookId = workbookId,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.few.api.web.support.method

@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
annotation class UserArgument
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.few.api.web.support.method

import com.few.api.security.authentication.token.TokenUserDetails
import org.springframework.security.core.GrantedAuthority

class UserArgumentDetails(
val isAuth: Boolean,
authorities: List<GrantedAuthority>,
id: String,
email: String,
) : TokenUserDetails(
authorities = authorities,
id = id,
email = email
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.few.api.web.support.method

import com.few.api.security.authentication.authority.AuthorityUtils
import com.few.api.security.filter.token.AccessTokenResolver
import com.few.api.security.token.TokenResolver
import io.github.oshai.kotlinlogging.KotlinLogging
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

@Component
class UserArgumentHandlerMethodArgumentResolver(
private val tokenResolver: TokenResolver,
) : HandlerMethodArgumentResolver {
val log = KotlinLogging.logger {}

override fun supportsParameter(parameter: MethodParameter): Boolean {
return parameter.hasParameterAnnotation(UserArgument::class.java)
}

override fun resolveArgument(
parameter: MethodParameter,
mavContainer: ModelAndViewContainer?,
webRequest: NativeWebRequest,
binderFactory: WebDataBinderFactory?,
): UserArgumentDetails {
val authorization: String? = webRequest.getHeader("Authorization")

val memberId = authorization?.let {
AccessTokenResolver.resolve(it)
}.let {
tokenResolver.resolveId(it)
} ?: 0L

val email = authorization?.let {
AccessTokenResolver.resolve(it)
}.let {
tokenResolver.resolveEmail(it)
} ?: ""

val authorities = authorization?.let {
AccessTokenResolver.resolve(it)
}?.let {
tokenResolver.resolveRole(it)
}?.let {
AuthorityUtils.toAuthorities(it)
} ?: emptyList()

return UserArgumentDetails(
isAuth = authorization != null,
id = memberId.toString(),
email = email,
authorities = authorities
)
}
}

0 comments on commit 6aa3191

Please sign in to comment.