/api/auth/login Path로 로그인 요청이 오면, EmailPasswordAuthenticationFilter가 가로채어 (사진의 인증을 처리하는 필터 Authentication Filter 구현체이다.) attemptAuthentication() 메소드를 실행
EmailPasswordAuthenticationFilter의 정확한 상속관계
attemptAuthentication() 메소드 내부 동작흐름
위의 메소드 실행으로 AuthenticationManager의 구현체 중 하나인, ProviderManager의 authenticate()가 실행된다. **ProviderManager.**authenticate()를 살펴보면, ProviderManager가 가지고 있는 List<AuthenticationProvider>에서 해당 Authentication을 처리할 수 있는 AuthenticationProvider를 찾는다.
ProviderManager.authenticate()메소드에 대한 설명을 읽어보면, Authentication object를 처리할 수 있는 AuthenticationProvider를 찾는다.
만약 여러 개의 Provider가 해당 인증 객체를 처리할 수 있다면?
만약 모든 Provider가 해당 인증객체를 처리 할 수 없다면?
먼저, 부모 AuthenticationManager가 있다면 부모에게 인증을 위임한다.
만약, 부모 인증 과정이 실패하는 경우 마지막으로 발생한 AuthenticationException이 최종적으로 던져진다!
if (result == null && this.parent != null) {
parentResult = this.parent.authenticate(authentication);
}
Provider를 찾았다고 가정하자!
여기서, ProviderManager에서 궁금한 점🤨
다시 짚고 넘어가면, 현재 프로젝트에서 사용 중인 인증 객체(Authentication)는 UsernamePasswordAuthenticationToken이다.
해당 인증 객체를 처리 할 수 있는 Provider는 DaoAuthenticationProvider이다. 따라서 현재 구현사항에 맞게 Provider는 DaoAuthenticationProvider에 집중하여 설명할 것이다.
// ProviderManager.authenticate 메소드
try {
//**DaoAuthenticationProvider.authenticate()**호출!
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
이제 AbstractUserDetailsAuthenticationProvider.authenticate()를 깊이있게 살펴보자
먼저 캐시에서 사용자 정보(UserDetails)가 있는지 조회한다.
retrieveUser메소드에서 UserDetailsService에서 사용자 정보 조회
타이밍 공격을 간단히 설명하자면, 존재하는 계정의 요청 시간과, 존재하지 계정의 요청 시간이 다름을 이용해, 유효한 계정을 찾아내는 공격을 의미한다.
따라서 Security는 prepareTimingAttackProtection()를 통해, 유효하지 않는 사용자의 경우에도 Password를 비교하는 로직을 수행할 수 있도록, 임의의 문자열(USER_NOT_FOUND_PASSWORD)을 해싱한(encode)한 값을 유효하지 않은 사용자의 비밀번호로 필드 값에 저장해두고, 비밀번호 비교 로직 실행 유무에 따른 시간 차이를 없애기 위한 준비를 해두는 것이다.
자세한 내용은 추후 학습하고 문서화 할 계획이다.
내부적으로 Provider를 찾고 UserService에 접근하는 로직은 별도로 작성하지 않음.(이미 구현되어 있어서 그냥 쓰면됨) 구체적으로 설명하면, Authentication을 인자로 받은 authenticationManager
는(즉, 구현체는 ProvideManager) 해당Authentication을 기반으로 provider를 찾는다. 즉, 현 상황에서는 Authentication을 상속받은 UsernamePasswordAuthenticationToken
에 맞는 provider를 찾아줄 것. authenticationManager
구현체인, providerManager 코드 읽어보면 감옴
즉, provider를 찾으면, 해당 provider는 userDetailsService에 UsernamePasswordAuthenticationToken 의 사용자 username(현 구현에서는
username필드에 email을 넣어둠) 을 보내어 해당 정보를 DB에서 찾음
db에서 찾아 userdeatils에 담아 다시 provider에 보내줌