BackEnd/Spring Security

[Spring Security] Authentication Flow

샤아이인 2022. 8. 27.

본 글은 Spring Security docs 와 여러 블로그 들을 참고하고, 공부하면서 요약하였습니다.

 

https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html#servlet-authentication-abstractprocessingfilter

 

Servlet Authentication Architecture :: Spring Security

ProviderManager is the most commonly used implementation of AuthenticationManager. ProviderManager delegates to a List of AuthenticationProviders. Each AuthenticationProvider has an opportunity to indicate that authentication should be successful, fail, or

docs.spring.io

 

1. Authentication Flow

전반적인 인증과정의 흐름은 다음과 같다.

 

1. Client 에서 로그인 요청


2. UsernamePasswordAuthenticationFIlter에서 사용자의 ID + PASSWORD를 담은 인증객체(Authentication)를 생성한다.

    참고로 UsernamePasswordAuthenticationFilter는 AbstractAuthenticationProcessingFilter 상속하고 있다.


3. AuthenticationManager에서 AuthenticationProvider에게 인증객체를 넘기며 인증처리를 위임한다.
→ 내부에 한 개 이상의 Provider를 담은 List를 가지고 있고 그 List에서 적절한 Provider를 찾아 위임시킨다.

→ AuthenticationManager는 실제 인증처리를 하기 보다는 적합한 Provider를 찾아서 위임하는 역할을 한다.

 

4. 해당 AuthenticationProvider는 input 정보(id, password)를 가지고 실제 인증 처리 역할을 한다.
    a. loadUserByUsername(username)메서드를 호출해서 Service에게 유저객체를 요청한다.
    b. UserDetailsService 인터페이스에게 loadUserByUsername(username) 요청
        i. Repository에 findById() 메서드로 유저 객체 조회. 
        ii. 만약, 해당 유저객체가 존재하지 않으면 UsernameNotFoundException이 발생한다.

             해당 예외는 UsernamePasswordAuthenticationFIlter에서 예외를 처리한다. → FailHandler()에서 후속처리
        iii. 만약, 존재한다면 AuthenticationProvider에서 Password를 검증한 후, 인증된다면 UserDetails 타입으로 반환된다.

             일치하지 않을 경우 BadCredentialException 발생 후 인증 실패


5. 인증관리자(AuthenticationManager)는 전달받은 Authentication을 UsernamePasswordAuthenticationFIlter로 반환한다.

6. SecurityContext에 저장한다.

 

7. 이후 전역적으로 SecurityContextHolder에서 인증객체를 사용가능하게 된다.

 

 

2. 디버깅 해보기

맨 처음 사용자가 Form 로그인을 하면 다음과 같이 UsernamePasswordAuthenticationFilter에서 걸리게 된다.

사용자가 입력한 정보를 기반으로 인증객체인 UsernamePasswordAuthenticationToken 을 생성했다.

AuthenticationManager를 찾아와 authRequest(token)를 전달하면서 인증을 위임하고 있다.

 

다음으로는 AuthenticationManager의 구현체인 ProviderManager에 도달한다.

내부적으로는 AuthenticationProvider를 List로 가지고 있다.

이중 적합한 AuthenticationProvider를 선택하게 된다.

 

여러 Provider중에서 DaoAuthenticationProvider를 찾아와 Form 방식의 로그인을 인증한다.

 

이후 DaoAuthenticationProvider의 내부를 보면 retrieveUser 메서드에서 UserDetailService를 통해 username으로 사용자를 찾아오는 코드가 다음과 같다.

여기서 loadedUser를 성공적으로 찾아온다면 ID 부분은 검증이 된것이다.

 

이제 찾아온 UserDetail을 기반으로 DaoAuthenticationProvider에서 Password만 검증하면 된다.

 

이후 AbstractUserDetailsAuthenticationProvider 에서 최종적으로 인증에 성공한 인증객체를 생성하고, 반환한다.

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;
}

Provider가 인증객체를 ProviderManager에게 반환해준다.

 

ProviderManager는 이를 다시 Filter로 전달한다.

AbstractAuthenticationProcessingFilter는 SecurityContext를 ContextHolder에 등록하여 인증처리가 끝난다.

참고로 UsernamePasswordAuthenticationFilter는 AbstractAuthenticationProcessingFilter 상속하고 있다.

댓글