Spring Filter Security로 인가처리 - Spring Filter Hands On 3
개요
Spring Security Hands On 시리즈에 들어가야 할 내용 같다고 생각하지만.. Spring Security를 활용하면 조금 더 편리하게 인가처리가 가능하기 때문에 소개하고자 합니다.
Spring Security 의존성 추가
implementation("org.springframework.boot:spring-boot-starter-security")
테스트용 Controller 구현
@RestController
class AuthorizationTestController {
@GetMapping("/admin/admin-path")
fun onlyAdmin(@RequestHeader role: String): String{
return "접근 가능"
}
@GetMapping("/user-path")
fun onlyUser(@RequestHeader role: String): String{
return "접근 가능"
}
}
SecurityConfig 설정
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
class SecurityConfig {
@Bean
fun filterChain(httpSecurity: HttpSecurity): SecurityFilterChain {
httpSecurity
.csrf {
it.disable()
}
.authorizeHttpRequests {
it
.requestMatchers("/admin/**").authenticated()
.requestMatchers("/**").permitAll()
}
.addFilterBefore(AuthorizationFilter(), UsernamePasswordAuthenticationFilter::class.java)
return httpSecurity.build()
}
}
admin이라는 path가 붙으면 인증을 수행하고, 그렇지 않으면 인증을 수행하지 않도록 구성합니다.
그리고 직접 구현할 AuthroizationFilter를 추가해 둡니다.
@EnableMethodSecurity의 경우에 추후에 @PreAuthorize, @PostAuthorize 등의 어노테이션을 활용하기 위해 선언합니다.
AuthorizationFilter 구현
class AuthorizationFilter : OncePerRequestFilter() {
override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain) {
val isFail = runCatching {
val role = request.getHeader("role").uppercase()
val authorities: List<GrantedAuthority> = listOf(SimpleGrantedAuthority("$ROLE_PREFIX$role"))
val authentication: Authentication = UsernamePasswordAuthenticationToken(null, "", authorities)
SecurityContextHolder.getContext().authentication = authentication
chain.doFilter(request, response)
}.isFailure
if (isFail) {
response.sendError(HttpStatus.UNAUTHORIZED.value(), "현재 권한으로는 접근할 수 없는 uri입니다.")
}
}
companion object{
const val ROLE_PREFIX = "ROLE_"
}
}
이전에 Filter를 구현했을 때와 동일하게 OncePerRequestFilter를 활용했습니다.
role을 받아오고 GrantedAuthority 객체를 만들어 넘기고 Authentication 객체를 만듭니다.
GrantedAuthority객체는 현재 사용자가 가지고 있는 권한을 의미하며 ROLE_ADMIN, ROLE_USER와 같이 ROLE_* 형태로 사용됩니다.
추후에 GrantedAuthority 객체를 활용하여 인가를 수행할 수 있습니다.
SecurityContextHolder에서 Context를 가져와 해당 인증객체를 저장해 둡니다.
해당 구현은 Thread Local을 활용하기 때문에 추후에 호출되는 스레드들은 인증객체의 정보를 활용할 수 있게 됩니다.
위의 코드를 한 줄로 요약하자면 헤더에서 값을 꺼내와 Role으로 사용자의 인가를 부여하여 인증이 완료된 객체를 생성해 냅니다.
MethodSecurity 방식 활용
@RestController
class AuthorizationTestController {
@PreAuthorize("hasRole('ROLE_ADMIN')")
@GetMapping("/admin/admin-path")
fun onlyAdmin(@RequestHeader role: String): String{
return "접근 가능"
}
@GetMapping("/user-path")
fun onlyUser(@RequestHeader role: String): String{
return "접근 가능"
}
}
@PreAuthorize 메서드를 활용하여 ROLE_ADMIN인 경우에만 onlyAdmin을 호출할 수 있도록 설정할 수 있습니다.
이제 동료 개발자들이 굳이 SecurityConfig의 필터설정을 보지 않더라도 해당 API는 권한이 필요하다는 것을 알 수 있습니다.
또한 ADMIN 이외에 SUPER_ADMIN, 협력사들의 권한들을 다채롭게 부여하여 다양한 인가를 유연하게 수행할 수 있습니다.
치명적인 단점
해당 방법은 Security를 잘 이해하지 못하고 사용했기 때문에 모든 Path에 AuthorizationFilter가 적용됩니다.
Security의 아키텍처를 이해하는 시간을 가지고 다음 방법을 통해 해결할 수 있습니다.
https://junuuu.tistory.com/859