ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Security의 인증을 커스텀 해보자.(3) - AuthenticationProvider, AuthenticationManger
    Spring Security 2023. 5. 20. 21:49

     이번 포스팅에서는 AuthenticationProvider의 구현 및 AuthenticationManger에 AuthenticationProvider을 등록하는 과정을 살펴볼 것이다.

    1. AuthenticationProvider

    • AuthenticationFiter로부터 Authentication을 전달받아 실제 인증을 담당한다.
    • AuthenticationProvider는 인증 처리를 위한 함수를 정의한 Interface이다.
    • 우리는 해당 Inferface를 상속받아 우리의 Authentication 인증 처리를 위한 HmacAuthenticationProviderJwtAuthenticationProvider를 구현하였다.
    public interface AuthenticationProvider {
        // 실제 인증 처리를 담당하는 함수이다.
        Authentication authenticate(Authentication authentication) throws AuthenticationException;
    
        // 어떤 Authentication을 인증할것인지 정의하는 함수이다.
        boolean supports(Class<?> authentication);
    }

    1-1. HmacAuthenticationProvider

    • HmacAuthenticationToken의 인증을 담당할 클래스이다.
    • HmacAuthenticationToken에서 clientId를 가져와 DB로부터 해당 clientId를 가진 Member가 있는지 확인한다.
    • 조회된 Member로부터 clientSecret를 가져와 전달받은 message(payload)를 암호화하여 HamcSignature를 만든다.
    • 클라이언트로부터 전달받은 HamcSignature와 우리가 만든 HamcSignature를 비교하여 Member를 인증한다.
    • 인증이 완료되면 Principal을 만들고 HmacAuthenticationToken에 주입하여 반환한다.
    class HmacAuthenticationProvider(
        private val hmacSignatureVerifier: HmacSignatureVerifier,
        private val memberRepository: MemberRepository,
        private val encryptionUtil: EncryptionUtil
    ) : AuthenticationProvider {
    
        override fun authenticate(authentication: Authentication?): Authentication {
            val hmacAuthentication = authentication as HmacAuthenticationToken
            // HmacAuthenticationToken에서 clientId를 가져와 DB로 부터 해당 clientId를 가진 Member가 있는지 확인한다.
            val member = memberRepository.findByKeyClientId(authentication.clientId!!)
                ?: throw UsernameNotFoundException("Customer(clientId=${authentication.clientId}) not found")
    
            val memberKey = member.key
    
            // hmacSignature의 검증 과정은 hmacSignatureVerifier 클래스가 담당하도록 위임하였다.
            val isValid = hmacSignatureVerifier.verify(
                signature = hmacAuthentication.signature!!,
                clientSecret = encryptionUtil.decrypt(memberKey.clientSecret),
                payload = hmacAuthentication.payload!!
            )
    
            if (!isValid) {
                throw BadCredentialsException("Invalid hmac authentication - clientId: ${hmacAuthentication.clientId}")
            }
    
            // 인증이 완료되면 Principal을 만들고 HmacAuthenticationToken에 주입하여 반환한다.
            val principal = CommonPrincipal(clientId = memberKey.clientId, roles = member.roles)
    
            return HmacAuthenticationToken.authenticated(
                principal = principal,
                authorities = member.roles.map { SimpleGrantedAuthority("ROLE_$it") }.toMutableList()
            )
        }
    
        // HmacAuthenticationToken만을 검증하도록 정의하였다.
        override fun supports(authentication: Class<*>): Boolean {
            return HmacAuthenticationToken::class.java.isAssignableFrom(authentication)
        }
    }

    1-2. JwtAuthenticationProvider

    • JwtAuthenticationToken의 인증을 담당할 클래스이다.
    • 해당 클래스에서는 Jwt토큰이 우리의 서버에서 발급한 토큰이 맞는지, 토큰의 유효기간을 지나지 않았는지 등 토큰을 검증한다.
    • Java-JWT 라이브러리를 이용하여 토큰 검증을 구현하였다.
    package com.flab.inqueue.security.jwt
    
    import com.flab.inqueue.security.common.CommonPrincipal
    import com.flab.inqueue.security.common.Role
    import com.flab.inqueue.security.jwt.utils.JwtUtils
    import com.flab.inqueue.security.jwt.utils.JwtVerificationResponse
    import io.jsonwebtoken.JwtException
    import org.springframework.security.authentication.AuthenticationProvider
    import org.springframework.security.authentication.BadCredentialsException
    import org.springframework.security.core.Authentication
    import org.springframework.security.core.authority.SimpleGrantedAuthority
    import org.springframework.stereotype.Component
    
    @Component
    class JwtAuthenticationProvider(
        private val jwtUtils: JwtUtils,
    ) : AuthenticationProvider {
    
        override fun authenticate(authentication: Authentication?): Authentication {
            val jwtAuthentication = authentication as JwtAuthenticationToken
    
            val verifyResponse: JwtVerificationResponse
            try {
                // JWT Token의 검증 과정은 JwtUtils 클래스가 담당하도록 위임하였다.
                verifyResponse = jwtUtils.verify(jwtAuthentication.jwtToken!!)
            } catch (e: JwtException) {
                throw BadCredentialsException("Invalid jwtToken - accessToken: ${authentication.jwtToken}", e)
            }
    
            // 인증이 완료되면 Principal을 만들고 JwtAuthenticationToken에 주입하여 반환한다.
            val principal = CommonPrincipal(
                clientId = verifyResponse.clientId,
                userId = verifyResponse.userId,
                roles = listOf(Role.USER)
            )
    
            return JwtAuthenticationToken.authenticated(
                principal = principal,
                authorities = listOf(Role.USER).map { SimpleGrantedAuthority("ROLE_$it") }.toMutableList()
            )
        }
    
        // JwtAuthenticationToken만을 검증하도록 정의하였다.
        override fun supports(authentication: Class<*>): Boolean {
            return JwtAuthenticationToken::class.java.isAssignableFrom(authentication)
        }
    }

    2. AuthenticationManger

    • 우리가 구현한 Authentication인 HmacAuthenticationToken과 JwtAuthenticationToken을 인증하기에 적절한 AuthenticationProvicer을 찾아 인증을 위임한다.
    • 우리는 위 기능만으로 충분하므로 Security에서 제공해주는 AuthenticationManager의 구현체인 ProviderManger를 사용하였다.
    • ProviderManger에는 우리가 구현한 HmacAuthenticationProvider과 JwtAuthenticationProvider를 등록하였다.
    @Bean
    fun providerManager(
        hmacAuthenticationProvider: HmacAuthenticationProvider,
        jwtAuthenticationProvider: JwtAuthenticationProvider
    ): ProviderManager {
        return ProviderManager(hmacAuthenticationProvider, jwtAuthenticationProvider
    }
    • 위의 ProviderManager는 AuthenticationFilter에 등록하여 작동하도록 한다.

     

     이번 포스팅을 통해서 우리는 AuthenticationProvider의 구현 및 AuthenticationManger에 AuthenticationProvider을 등록하는 과정에 대해서 살펴보았으며, 다음 포스팅에서는 마지막으로 Securiy의 전체 구성을 담당하는 SecurityConfiguration 부분을 살펴볼 것이다.

     

    이번 포스팅의 구현 코드를 자세히 보고 싶다면, 우리 프로젝트의 Github를 통해서 살펴볼 수 있다.

    https://github.com/f-lab-edu/inqueue/tree/main/src/main/kotlin/com/flab/inqueue/security

Designed by Tistory.