-
Spring Security의 인증을 커스텀 해보자.(3) - AuthenticationProvider, AuthenticationMangerSpring Security 2023. 5. 20. 21:49
이번 포스팅에서는 AuthenticationProvider의 구현 및 AuthenticationManger에 AuthenticationProvider을 등록하는 과정을 살펴볼 것이다.
1. AuthenticationProvider
- AuthenticationFiter로부터 Authentication을 전달받아 실제 인증을 담당한다.
- AuthenticationProvider는 인증 처리를 위한 함수를 정의한 Interface이다.
- 우리는 해당 Inferface를 상속받아 우리의 Authentication 인증 처리를 위한 HmacAuthenticationProvider과 JwtAuthenticationProvider를 구현하였다.
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
'Spring Security' 카테고리의 다른 글
Spring Security의 인증을 커스텀 해보자.(2) - AuthenticationFilter, Authentication (0) 2023.04.27 Spring Security의 인증을 커스텀 해보자.(1) - Spring Security 개요 (0) 2023.04.25