Kotlin
[Kotlin] @PreAuthorize를 사용한 인가(Authorization)처리
keep it simple
2023. 4. 29. 11:28
Authorization이란?
- 인증(Authentication)받은 사용자가 가지고 있는 인가를 뜻한다. 인가는 유저가 가지고있는 권한(Role)을 확인해서 권한이 가지고있는 액션들만 행할 수 있다. 밑과 같이 예제를 들면 ADMIN/USER 권한이 있고 각 권한마다 할 수 있는 행동들이 정해져 있다. 인증된 유저가 권한이 해당되는 액션들만 할 수 있게 처리하는게 인가(Authorization)이다. Spring Security를 사용해 간단하게 구현해보자.
- ADMIN 권한: 회원 승인/ 회원차단
- USER 권한: 회원가입
data class SimpleAuthentication(
val user: User
) : Authentication {
override fun getAuthorities(): Collection<GrantedAuthority> =
if (user.role == "ADMIN") {
setOf(
SimpleGrantedAuthority("ROLE_"+ user.role)
)
} else {
setOf(
SimpleGrantedAuthority("ROLE_" + user.role)
)
}
}
- 위와 같이 코드로 인가받는 유저를 구현하면 이렇다. SimpleAuthentication은 authentication 인터페이스를 구현 후 getAuthorities()함수를 상속받는다. 해당 함수는 인가받은 권한을 담당한다. DB에서는 권한 관련해서 ROLE은 ADMIN 또는 USER이렇게 스트링 형태로 저장이된다. 상속받은 함수에서는 인증된 유저의 DB에있는 Role에 따라 SimpleGrantedAuthority를 통해 권한을 생성후 Collection에 담아준 후 리턴한다. 이제 해당 함수를 통해 담긴 인가를 통해 권한을 제어해보자.
- 처음엔 SimpleGrantedAuthority에 auth.role만 담아주었는데 안되었다. 무조건 앞에 프리픽스로 ROLE_을 붙혀줘야 작동이 된다.
Configure
- 밑과 같이 SecurityFilterChain을 상속받은 필터 체인에 authorizeRequests옵션을 통해 authorize()함수를 통해 접근 제어하고싶은 url 프리픽스와 해당 접근 권한을 설정할 수 있다. ROLE_ADMIN 권한을 가지고있는 유저만 /admin이 앞에 있는 url에 접근할 수 있다. 스프링 시큐리티 설정을 통해 제어하는 방법은 접근 범위가 큰 경우에는 좋지만 함수단위/클래스 단위로 제어하고싶을 때는 어렵다.
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
authorize("/admin/**",hasAnyRole("ROLE_ADMIN"))
}
formLogin { }
httpBasic { }
}
return http.build()
}
@PreAuthorize
@PreAuthorize("hasRole('ADMIN')")
@Target(AnnotationTarget.FUNCTION,AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class AuthorizedAdmin {
}
@PreAuthorize("hasRole('USER')")
@Target(AnnotationTarget.FUNCTION,AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class AuthorizedUser {
}
@AuthorizedAdmin
@RestController
@RequestMapping("admin/api/v1")
class AdminController(
) {
@GetMapping
fun get(
) ...
}
- 커스텀 어노테이션 AutheorizedAdmin/AuthorizedUser를 만들었다. 공통적으로 두 어노테이션은 @PreAuthorize에 hasRole에 권한만 다르게 설정했다. 이렇게 커스텀 어노테이션을 만든 이유는 컨트롤러나 함수에 @PreAuthorize("hasRole('ADMIN')") 직접적으로 달아줄 경우 나중에 권한 수정이 필요할 경우 일일히 수정해야되서 공통으로 사용해서 한번에 수정할 수 있게 만들었다.
- @AuthorizedAdmin을 달아준 api에 유저 권한으로 테스트를 하면 403 Fobidden status에 message는 권한없는 사용자라고 나타난다. 작동이 잘된다.
AccessDenidedHandler
- 인가가 실패한 경우 올바른 메세지나 에외처리도 중요하다. 그래서 AccessDeniedHandler를 상속받아 인가 실패한 유저한테 메세지를 커스톰해서 내려줄 수 있다.
class AuthorizationDeniedHandler : AccessDeniedHandler {
override fun handle(
request: HttpServletRequest?,
response: HttpServletResponse,
accessDeniedException: AccessDeniedException?
) {
response.run {
sendError(HttpStatus.FORBIDDEN.value(), "권한없는 사용자입니다.")
}
}
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
exceptionHandling { accessDeniedHandler = AuthorizationDeniedHandler() }
formLogin { }
httpBasic { }
}
return http.build()
}
- 첫번째 이미지처럼 AccessDeniedHandler를 상속받은 클래스를 구현한 후 SecurityFilterChain 구현한 클래스 안에 http 안에exceptionHandling { accessDeniedHandler = AuthorizationDeniedHandler() } 를 넣어주면 커스톰한대로 예외처리가 된다.
Reference