작성자

작성일

작성목적

작성 후기

먼저 현재 프로젝트에 구현된 securtiy의 시퀀스 정리를 구분하자

1. 로그인 시퀀스

image (4).png

  1. /api/auth/login Path로 로그인 요청이 오면, EmailPasswordAuthenticationFilter가 가로채어 (사진의 인증을 처리하는 필터 Authentication Filter 구현체이다.) attemptAuthentication() 메소드를 실행

  2. 위의 메소드 실행으로 AuthenticationManager의 구현체 중 하나인, ProviderManager의 authenticate()가 실행된다. **ProviderManager.**authenticate()를 살펴보면, ProviderManager가 가지고 있는 List<AuthenticationProvider>에서 해당 Authentication을 처리할 수 있는 AuthenticationProvider를 찾는다.

    스크린샷 2025-03-14 오후 4.28.56.png

  3. 다시 짚고 넘어가면, 현재 프로젝트에서 사용 중인 인증 객체(Authentication)는 UsernamePasswordAuthenticationToken이다.

    스크린샷 2025-03-14 오후 4.48.50.png

    해당 인증 객체를 처리 할 수 있는 Provider는 DaoAuthenticationProvider이다. 따라서 현재 구현사항에 맞게 Provider는 DaoAuthenticationProvider에 집중하여 설명할 것이다.

    // ProviderManager.authenticate 메소드
    try {
    				//**DaoAuthenticationProvider.authenticate()**호출!
    				result = provider.authenticate(authentication); 
    				if (result != null) {
    					copyDetails(authentication, result);
    					break;
    				}
    			}
    
  4. 이제 AbstractUserDetailsAuthenticationProvider.authenticate()를 깊이있게 살펴보자

    스크린샷 2025-03-14 오후 5.23.00.png

    1. 입력된 authentication 객체가 UsernamePasswordAuthenticationToken인지 확인한다.
      • 만약 다른 타입의 Authentication 객체가 전달되면 메시지와 함께 예외를 발생한다. (IllegalArgumentException)

    스크린샷 2025-03-14 오후 5.24.57.png

    1. 사용자 이름(username) 추출
      • 해당 클래스에 정의 되어 있는 determineUsername()를 통해 이름을 추출한다.
      • 앞서 우리 서비스에서 username에 email을 넣었음을 잊지 말자

    스크린샷 2025-03-14 오후 5.27.58.png

    1. 먼저 캐시에서 사용자 정보(UserDetails)가 있는지 조회한다.

    2. retrieveUser메소드에서 UserDetailsService에서 사용자 정보 조회

      • if문 안에서 본격적인 사용자 인증 로직을 시작한다.
      • 코드의 try문에서 retrieveUser()문을 호출하여, 사용자를 검색한다. retrieveUser메소드는 앞서 말한대로, DaoAuthenticationProvider에서 재정의(override)하여 구현 되어 있다. 따라서 DaoAuthenticationProvider.retrieveUser 메소드를 살펴보자

      스크린샷 2025-03-14 오후 10.01.27.png

      • 먼저 인자로, String username**, UsernamePasswordAuthenticationToken** authentication를 받는다. 즉, 해당 메소드는 첫번째 인자인 username으로 DB에서 사용자를 찾고, 두번째 인자 authentication에 들어있는 password를 비교하여, 사용자 인증을 진행한다.
      • 본격적인 사용자 검색에 앞서, 한가지 보안 작업을 미리 수행한다. prepareTimingAttackProtection()을 실행하는데, 이는 타이밍 공격(Timing Attack)을 막기 위한 보안 기능이다.
        • 타이밍 공격을 간단히 설명하자면, 존재하는 계정의 요청 시간과, 존재하지 계정의 요청 시간이 다름을 이용해, 유효한 계정을 찾아내는 공격을 의미한다.

          스크린샷 2025-03-14 오후 10.27.31.png

          스크린샷 2025-03-14 오후 10.24.04.png

        • 따라서 Security는 prepareTimingAttackProtection()를 통해, 유효하지 않는 사용자의 경우에도 Password를 비교하는 로직을 수행할 수 있도록, 임의의 문자열(USER_NOT_FOUND_PASSWORD)을 해싱한(encode)한 값을 유효하지 않은 사용자의 비밀번호로 필드 값에 저장해두고, 비밀번호 비교 로직 실행 유무에 따른 시간 차이를 없애기 위한 준비를 해두는 것이다.

        • 자세한 내용은 추후 학습하고 문서화 할 계획이다.

      • 먼저, Authentication 객체(즉, 사용자한테 입력받은 username(우리는 username에 email이 저장), password)에서 email을 꺼내어 UserDetailsService에서 사용자 정보를 조회한다.
      • 해당 조회
  5. 내부적으로 Provider를 찾고 UserService에 접근하는 로직은 별도로 작성하지 않음.(이미 구현되어 있어서 그냥 쓰면됨) 구체적으로 설명하면, Authentication을 인자로 받은 authenticationManager는(즉, 구현체는 ProvideManager) 해당Authentication을 기반으로 provider를 찾는다. 즉, 현 상황에서는 Authentication을 상속받은 UsernamePasswordAuthenticationToken에 맞는 provider를 찾아줄 것. authenticationManager 구현체인, providerManager 코드 읽어보면 감옴

  6. 즉, provider를 찾으면, 해당 provider는 userDetailsService에 UsernamePasswordAuthenticationToken 의 사용자 username(현 구현에서는 username필드에 email을 넣어둠) 을 보내어 해당 정보를 DB에서 찾음

  7. db에서 찾아 userdeatils에 담아 다시 provider에 보내줌