경주장

[스프링 시큐리티 주요 아키텍처 이해] Authentication Flow 본문

스프링/스프링 시큐리티

[스프링 시큐리티 주요 아키텍처 이해] Authentication Flow

달리는치타 2022. 2. 8. 17:17

username-password authentication flow


AuthenticationFilter

Filter는 AuthenticationManager에게 UsernamePasswordAuthenticationToken 첫번째 생성자 (id, password)를 통해 인증 토큰을 생성하여 인증의 책임을 위임합니다.

UsernamePasswordAuthenticationFilter ln44-46


AuthenticationManager의 구현체 ProviderManager는 중요하게는 아래의 두가지 필드를 가지고 있습니다.

private List<AuthenticationProvider> providers;
private AuthenticationManager parent;
  • providers는 실질적으로 인증을 담당할 AuthenticationProvider를 List로 가지는 필드입니다.
  • AuthenticationManager는 자신과 같은 타입의 객체를 parent란 이름으로 부모로 가집니다.
    • 자신의 providers 리스트를 모두 조회 하여도 주어진 authentictionToken을 지원(support)하는 타입의 provider가 없다면 부모에게 그 인증의 책임을 위임합니다.
    • if (result == null && this.parent != null) {
           result = this.parent.authenticate(authentication);
      }

AuthenticationProvider

AuthenticationProvider는 authenticationManger로부터 인증의 책임을 위임받은 실질적인 인증의 주체입니다.

public interface AuthenticationProvider {
    Authentication authenticate(Authentication authentication) throws AuthenticationException;

    boolean supports(Class<?> authentication);
}

 

 

UsernamePasswordAuthenticationToken의 경우 AbstractUserDetailsAuthenticationProvider를 상속받은 authenticationProvider가 support합니다. SpringSecurity에서는 구현체로 DaoAuthenticationProvider를 제공합니다. 

//AbstractUserDetailsAuthenticationProvider.supports
public boolean supports(Class<?> authentication) {
    return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}

 

DaoAuthenticationProvider의 authenticate 매서드는 부모 추상클래스에 정의 되어있습니다.

authenticate 매서드는 userDetailsService를 활용하여 userDetails를 load하고 사용자의 username, credentials에 대한 검증을 수행합니다. 

UserNotFoundException, BadCredentialsException등이 발생하지 않는다면 성공적인 인증입니다.

 public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        if(user not found){
        	throw new UsernameNotFoundException();
        }
     	if(!passwordEncoder.matches(presentedPassword, userDetails.getPassword()){
        	throw new BadCredentialsException();
        }

        return this.createSuccessAuthentication(principalToReturn, authentication, user);
    }

   
   protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
        result.setDetails(authentication.getDetails());
        this.logger.debug("Authenticated user");
        return result;
    }
  • UserDetailsService를 통해 userDetails를 load하여 주어진 authentication객체와 비교하여 user not found를 검증
    • load결과가 null이라면 user not found DaoAuthenticationProvider.InMemoryUserDetailsManager.loadUserByUsername (구현부, 따로 설정 안할 경우)
  • passwordEncoder를 활용하여 BadCredentialException을 확인하는 과정 - DaoAuthenticationProvider.retriveUser

 

예외발생이 없다면 createSuccessAuthentication 매서드에서 UsernamePasswordAuthenticationToken의 두번째 생성자를 통해 인증 성공 토큰이 생성되고 AuthenticationManager에게 전달됩니다. 


UserDetailsService

username을 통해 User객체를 찾고 UsernameNotFoundException을 확인하는 책임을 지닌 인터페이스 입니다.

public interface UserDetailsService {
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

 

//InMemoryUserDetailsManager

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserDetails user = (UserDetails)this.users.get(username.toLowerCase());
        if (user == null) {
            throw new UsernameNotFoundException(username);
        } else {
            return new User(user.getUsername(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(), user.isCredentialsNonExpired(), user.isAccountNonLocked(), user.getAuthorities());
        }
    }