글 작성자: beaniejoy

개인 프로젝트 하면서 적용했던 내용을 정리해보는 글입니다. 이번 내용은 로그인(인증) 프로세스를 순수 Spring Security만을 가지고 개발해보았던 내용을 두 번에 나누어 정리해보고자 합니다.

이번 게시글에서는 api 인증 프로세스 개발 적용해보기 전에 Spring Security의 기본 인증 전략에 대해서 간단하게 정리해보고자 합니다.
(이부분을 알고 있어야 api용 인증 프로세스 개발할 때 사용되는 Filter, Provider, UserDetailsService의 custom 구현체들이 어떻게 적용되는지 알 수 있습니다.)

 

📌 1. 간단한 Spring Security 인증 과정

Spring Security에서 간단한 인증 과정을 살펴보면 다음과 같습니다. (자세한 내용은 생략하도록 하겠습니다.)

1. 사용자 Form 로그인 화면
-> 2. AbstractAuthenticationProcessingFilter (doFilter)
    -> UsernamePasswordAuthenticationFilter (attemptAuthentication 호출)
-> 3. AuthenticationManager(ProviderManager)
-> 4. AuthenticationProvider
    -> UserDetailsService (loadUserByUsername 호출)
-> 5. AbstractAuthenticationProcessingFilter (UsernamePasswordAuthenticationFilter의 추상 클래스)
    -> 여기서 인증 성공시 AuthenticationSuccessHandler 호출
    -> 인증 실패 시 AuthenticationFailureHandler 호출

실제로 Spring Security의 인증 과정은 위의 과정보다 훨씬 더 많은 Filter들을 거쳐서 이루어지고 중간에 많은 과정을 거치게 되는데요. 이번에 인증 과정을 개발하면서 중요한 지점들만 추려보았습니다. 각 과정에 대해서 간단하게 정리해보고자 합니다.

 

📌 2. 사용자 Form 로그인 화면

Spring Security를 프로젝트에 적용하면 기본적으로 Form Login 방식을 사용하게 됩니다. 모든 요청에 대해서 authenticated 설정을 하게 되면 애플리케이션을 실행하고 브라우저에서 요청을 하게 되면 Spring Security 기본 로그인 Form 화면으로 이동하게 됩니다.

여기서 사용자 ID와 비밀번호 입력 후 확인 버튼을 누르게 되면 각각 username, password라는 parameter 이름으로 인증을 요청하게 됩니다.

 

📌 3. UsernamePasswordAuthenticationFilter

AbstractAuthenticationProcessingFilter를 상속받은 Spring Security Filter 클래스입니다. Form Login 방식으로 인증 요청을 하면 여기서 요청을 받게 됩니다.
(정확히는 AbstractAuthenticationProcessingFilter로 들어오게 되고 doFilter에서 인증 시도하는 메소드를 호출하게 되는데 이 때 사용되는 클래스가 UsernamePasswordAuthenticationFilter입니다.)

UsernamePasswordAuthenticationFilter.java 코드 일부분

obtainUsername, obtainPassword를 통해 username, password라는 parameter를 request에서 받아 옵니다. 그리고 새로 만든 Authentication 객체에 두 개의 값을 담아 AuthenticationManager로 넘깁니다.

 

📌 4. AuthenticationManager(ProviderManager)

여기서는 실제 인증 처리를 담당할 Provider들을 list로 가지고 있는데요. 반복문 수행을 통해 list를 돌면서 Provider에게 인증처리를 위임하게 됩니다. AuthenticationManager는 인터페이스고 실제로는 ProviderManager 구현체가 담당하게 됩니다.

AuthenticationManager(ProviderManager)의 코드 일부분

getProviders()를 통해서 등록된 AuthenticationProvider list를 가져옵니다. 가져온 Provider list를 반복문으로 돌면서 인증 처리를 위임하게 됩니다.

중요한 것은 인증 처리를 위임했는데 결과가 null(지원하지 않는 Authentication class type 등의 사유로)인 경우에 parent ProviderManager를 계속 탐색할 수 있다는 점입니다. Security 설정시 원할 경우 parent ProviderManager를 등록해서 인증 처리를 위한 Provider 탐색을 연장할 수 있습니다. 

 

📌 5. AuthenticationProvider

여기서 실제 인증처리를 담당하게 됩니다. 코드 내용은 다음과 같습니다.

AbstractUserDetailsAuthenticationProvider 코드 일부분, 여기서 실질적인 인증 과정을 거치게 된다.

먼저 retrieveUser 메소드를 통해 UserDetailsService 구현체에서 username을 가지고 사용자를 조회해 UserDetails 객체를 받아옵니다. username으로 User 조회에 실패하면 UsernameNotFoundException을 반환하게 됩니다.

그다음 additionalAuthenticationChecks를 통해서 input 받은 password와 조회한 UserDetails password의 일치여부를 체크하게 됩니다. 여기서 password 값이 일치하지 않으면 BadCredentialsException 에러를 throw하게 됩니다.

인증 과정에 이상이 없으면 UsernamePasswordAuthenticationToken(Authentication) 객체를 만들어 반환하게 됩니다.

 

📌 6. AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilter 코드 일부분(doFilter)

UsernamePasswordAuthenticationFilter의 추상 클래스입니다. attemptAuthentication 메소드는 위에 2번 과정에서 설명드렸던 UsernamePasswordAuthenticationFilterusername, password parameter를 받아 AuthenticationManager에게 Authentication 객체를 넘기는 과정입니다.

4번 과정까지 인증 과정을 거쳐 Authentication 객체를 받아오고 만약 인증을 성공하게 되면 successfulAuthentication 메소드를 호출하는데요. 여기서 AuthenticationSuccessHandler를 호출하게 됩니다.

인증 과정 중 username으로 UserDetails 조회에 실패하거나 password 불일치 등으로 인증을 실패하게 되면 unsuccessfulAuthentication를 호출하게 되고 여기서 AuthenticationFailureHandler를 호출하게 됩니다.

위의 과정은 개발하면서 적용되었던 부분들에 대해서만 언급한 내용입니다. 인증을 통과하게 되면 Session에 SecurityContext 저장하는 과정도 있고 인증 처리 전 후로 더 디테일한 과정들이 있지만 모두 생략하였습니다.

 

📌 7. 정리

Spring Security에서 form login 인증 적용시 기본적으로 수행하는 인증 프로세스에 대해서 정말 간략하게 정리해보았습니다.

form login 방식에서 위에서는 생략했지만 알아야하는 중요한 내용은 인증 성공시 반환받은 Authentication 객체를 SecurityContext에 저장하고 최종적으로 Session에 저장된다는 내용입니다. Spring Security는 기본적으로 session 정책을 사용하게 되는데요. 이로 인해 한계점도 분명 있어서 OAuth2, JWT같은 stateless 인증 방식도 등장했습니다.

이부분은 제쳐두고 위의 form login 인증 방식은 말 그대로 form tag에 POST method로 parameter로 username, password를 담아 요청한 내용에 대해서 다루고 있습니다.

Spring Security는 api 방식(json body)의 인증 프로세스를 지원하지 않기 때문에 이를 적용하려면 따로 Security 설정에 적용을 해야하는데요. 다음 게시글에서 api 인증 과정 적용을 위한 custom 구현체들을 Spring Security에 어떻게 적용하는지 이어서 정리해보고자 합니다.

틀린 내용이 있을 수 있습니다. 이에 대한 코멘트 언제나 환영합니다.