스프링 시큐리티 공식 문서를 읽고, 라이브러리 코드들을 뜯어보면서 공부했습니다.
인증은 username/password 인증을 기준으로 설명하겠습니다.
이전 장에서, UsernamePasswordAuthenticationFilter
나 BasicAuthenticationFilter
가 HttpServletRequest로부터 username/password를 받아서 UsernamePasswordAuthenticationToken
을 생성하는 과정을 알아봤습니다.
그리고 두 필터 모두 AuthenticationManager의 authenticate 메서드를 호출하는 것으로 이전 장을 마무리했습니다.
authenticate 메서드를 호출할 때,
UsernamePasswordAuthenticationToken
을 보냅니다.
이번 장에서 AuthenticationManager
가 어떻게 토큰을 인증하는지 알아보겠습니다.
AuthenticationManager
는 interface입니다.
스프링 시큐리티에서 다른 설정을 하지 않았다면 기본적으로 ProviderManager
가 구현 객체로 주입됩니다.
ProviderManager
는 authenticate 메서드의 인자로 받은 Authentication
객체의 인증을 할 수 있는 AuthenticationProvider
을 찾기 시작합니다.
AuthenticationProvider
는 interface입니다.
AuthenticationProvider
의 구현 객체들이ProviderManager
에 등록되어 있습니다.
(위 코드는 ProviderManager
의 authenticate 메서드의 일부 코드를 캡쳐한 사진입니다.)
173번 코드를 보면 여러 provider들을 for문을 통해 탐색하고 있습니다.
174번 코드는 해당 provider가 현재 받은 Authentication
객체를 인증할 수 있는지 검증하는 코드입니다.
Class<? extends Authentication> toTest = authentication.getClass();
해당 provider가 현재 받은 Authentication
객체를 인증할 수 없다면 다음 provider를 탐색합니다.
182번 코드를 보면, 해당 provider는 현재 받은 Authentication
객체를 인증할 수 있기 때문에, authentication을 인자로 보내면서 provider의 authenticate 메소드를 호출합니다.
여기서 UsernamePasswordAuthenticationToken
의 인증을 진행할 수 있는 AuthenticationProvider의 구현 객체는 DaoAuthenticationProvider
입니다.
따라서 ProviderManager
로부터 DaoAuthenticationProvider
의 authenticate 메서드가 호출됩니다.
그리고 DaoAuthenticationProvider
는 인증하는 중간에 UserDetailsService
의 loadUserByUsername
메서드를 호출합니다.
loadUserByUsername
메서드를 통해 입력받은 username에 해당하는 사용자가 DB에 있는지 조회합니다.
없다면 Exception 호출합니다.
username에 해당하는 사용자가 DB에 있다면, DaoAuthenticationProvider
는 해당 사용자 이름과 password를 비교해서 인증을 진행합니다.
인증에 성공하면 DaoAuthenticationProvider
가 Authentication 객체를 ProviderManager
로 반환합니다.
그리고 ProviderManager
도 Authentication 객체를 AuthenticationFilter에게 반환합니다.
마지막으로 AuthenticationFilter가
ProviderManager
(AuthenticationManager
의 구현 객체)로부터 받은 Authentication을 SecurityContextHolder에 저장합니다.
아래 두개의 그림은, 인터페이스에 구현객체가 주입되는 과정과 전체 로그인 과정에 대한 그림입니다.
(Form 로그인 기준)
UsernamePasswordAuthenticationFilter
가 사용되었습니다.
UsernamePasswordAuthenticationFilter
가 UsernamePasswordAuthenticationToken
객체의 인증을 위해, AuthenticationManger
의 authenticate 메서드를 호출합니다.
여기서
AuthenticationManager
의 구현 객체인ProviderManager
의 authenticate 메서드가 실행됩니다.
ProviderManager
는 현재 받은 토큰을 인증할 수 있는 Provider를 탐색합니다.
UsernamePasswordAuthenticationToken
을 인증할 수 있는 Provider는AuthenticationProvider
이기 때문에,AuthenticationProvider
의 authenticate 메서드를 호출합니다.여기서
AuthenticationProvider
의 구현 객체인DaoAuthenticationProvider
의 authenticate 메서드가 실행됩니다.
DaoAuthenticationProvider
는 입력받은 토큰으로부터, username이 현재 DB에 있는지 조회하기 위해, UserDetailsService
의 loadUserByUsername
메서드를 호출합니다.
UserDetailsService
는 입력받은 username을 DB에 조회합니다.
해당 유저가 있다면 유저 정보를 반환합니다.
없다면 예외가 발생합니다. (UsernameNotFoundException)
(성공기준) DaoAuthenticationProvider
는 UserDetailsService
로 부터 받은 유저정보의 password와 UsernamePasswordAuthenticationToken
의 password를 비교합니다.
password가 같다면(인증 성공), DaoAuthenticationProvider
는 성공된 토큰을 ProviderManager
에게 반환합니다. (DaoAuthenticationProvider
의 authenticate 메서드 종료)
ProviderManager
는 받은 토큰을 UsernamePasswordAuthenticationFilter
에게 반환합니다. (ProviderManager
의 authenticate 메서드 종료)
UsernamePasswordAuthenticationFilter
은 받은 토큰(UsernamePasswordAuthenticationToken
)을 SecurityContextHolder에 저장합니다.
다음 장에서,
AnonymousAuthenticationFilter
와ExceptionTranslationFilter
가 하는 역할에 대해 알아보겠습니다.
개인적으로 공부한 내용을 정리한 글입니다.
지적, 피드백 환영합니다.