ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Filter Security로 인가처리 - Spring Filter Hands On 3
    Spring Framework/Filter 2023. 10. 6. 00:01

    개요

    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

     

    댓글

Designed by Tistory.